From a3d2dd8366edb0a9f47e08211b92e6d6b3b78b4c Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Sat, 24 Jan 2026 15:15:01 +0800 Subject: [PATCH] Convert most Attribute related calls from []const u8 -> String --- src/browser/Page.zig | 46 +++---- src/browser/ScriptManager.zig | 12 +- src/browser/dump.zig | 12 +- src/browser/js/Caller.zig | 19 ++- src/browser/js/Local.zig | 12 +- src/browser/js/js.zig | 2 + src/browser/tests/element/html/input.html | 4 +- .../webapi/CustomElementDefinition.zig | 9 +- src/browser/webapi/CustomElementRegistry.zig | 4 +- src/browser/webapi/Document.zig | 20 +-- src/browser/webapi/DocumentFragment.zig | 2 +- src/browser/webapi/Element.zig | 46 +++---- src/browser/webapi/MutationObserver.zig | 12 +- src/browser/webapi/Node.zig | 17 +-- src/browser/webapi/ShadowRoot.zig | 2 +- .../webapi/collections/DOMTokenList.zig | 6 +- .../webapi/collections/HTMLAllCollection.zig | 2 +- .../HTMLFormControlsCollection.zig | 4 +- .../webapi/collections/RadioNodeList.zig | 8 +- src/browser/webapi/collections/node_live.zig | 14 +-- src/browser/webapi/element/Attribute.zig | 117 ++++++++---------- src/browser/webapi/element/DOMStringMap.zig | 49 ++++++-- src/browser/webapi/element/Html.zig | 2 +- src/browser/webapi/element/html/Anchor.zig | 18 +-- src/browser/webapi/element/html/Audio.zig | 8 +- src/browser/webapi/element/html/Body.zig | 2 +- src/browser/webapi/element/html/Button.zig | 26 ++-- src/browser/webapi/element/html/Canvas.zig | 8 +- src/browser/webapi/element/html/Custom.zig | 6 +- src/browser/webapi/element/html/Data.zig | 4 +- src/browser/webapi/element/html/Dialog.zig | 10 +- src/browser/webapi/element/html/Form.zig | 10 +- src/browser/webapi/element/html/Image.zig | 30 ++--- src/browser/webapi/element/html/Input.zig | 78 ++++++------ src/browser/webapi/element/html/Link.zig | 8 +- src/browser/webapi/element/html/Media.zig | 26 ++-- src/browser/webapi/element/html/Meta.zig | 16 +-- src/browser/webapi/element/html/Option.zig | 28 +++-- src/browser/webapi/element/html/Script.zig | 20 +-- src/browser/webapi/element/html/Select.zig | 28 ++--- src/browser/webapi/element/html/Slot.zig | 6 +- src/browser/webapi/element/html/Style.zig | 18 +-- src/browser/webapi/element/html/TextArea.zig | 18 +-- src/browser/webapi/element/html/Video.zig | 4 +- src/browser/webapi/net/FormData.zig | 6 +- src/browser/webapi/net/XMLHttpRequest.zig | 6 +- src/browser/webapi/selector/List.zig | 20 +-- src/browser/webapi/selector/Parser.zig | 5 +- src/cdp/AXNode.zig | 52 ++++---- src/string.zig | 21 ++-- 50 files changed, 486 insertions(+), 417 deletions(-) diff --git a/src/browser/Page.zig b/src/browser/Page.zig index 6fbd184a..47c09c37 100644 --- a/src/browser/Page.zig +++ b/src/browser/Page.zig @@ -1037,7 +1037,7 @@ pub fn scriptAddedCallback(self: *Page, comptime from_parser: bool, script: *Ele self._script_manager.addFromElement(from_parser, script, "parsing") catch |err| { log.err(.page, "page.scriptAddedCallback", .{ .err = err, - .src = script.asElement().getAttributeSafe(comptime .literal("src")), + .src = script.asElement().getAttributeSafe(comptime .wrap("src")), }); }; } @@ -1122,7 +1122,7 @@ pub fn getElementByIdFromNode(self: *Page, node: *Node, id: []const u8) ?*Elemen } var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(node, .{}); while (tw.next()) |el| { - const element_id = el.getAttributeSafe(comptime .literal("id")) orelse continue; + const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue; if (std.mem.eql(u8, element_id, id)) { return el; } @@ -1696,7 +1696,7 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const // If page's base url is not already set, fill it with the base // tag. if (self.base_url == null) { - if (n.as(Element).getAttributeSafe(comptime .literal("href"))) |href| { + if (n.as(Element).getAttributeSafe(comptime .wrap("href"))) |href| { self.base_url = try URL.resolve(self.arena, self.url, href, .{}); } } @@ -2074,9 +2074,9 @@ pub fn createElementNS(self: *Page, namespace: Element.Namespace, name: []const while (it.next()) |attr| { Element.Html.Custom.invokeAttributeChangedCallbackOnElement( element, - attr._name.str(), + attr._name, null, // old_value is null for initial attributes - attr._value.str(), + attr._value, self, ); } @@ -2236,7 +2236,7 @@ pub fn createProcessingInstruction(self: *Page, target: []const u8, data: []cons } // Validate target follows XML name rules (similar to attribute name validation) - try Element.Attribute.validateAttributeName(target); + try Element.Attribute.validateAttributeName(.wrap(target)); const owned_target = try self.dupeString(target); const owned_data = try self.dupeString(data); @@ -2307,7 +2307,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts if (parent.is(Element)) |parent_el| { if (self._element_shadow_roots.get(parent_el)) |shadow_root| { // Signal slot changes for any affected slots - const slot_name = el.getAttributeSafe(comptime .literal("slot")) orelse ""; + const slot_name = el.getAttributeSafe(comptime .wrap("slot")) orelse ""; var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(shadow_root.asNode(), .{}); while (tw.next()) |slot_el| { if (slot_el.is(Element.Html.Slot)) |slot| { @@ -2346,7 +2346,7 @@ pub fn removeNode(self: *Page, parent: *Node, child: *Node, opts: RemoveNodeOpts // the ID map and invoking disconnectedCallback for custom elements var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{}); while (tw.next()) |el| { - if (el.getAttributeSafe(comptime .literal("id"))) |id| { + if (el.getAttributeSafe(comptime .wrap("id"))) |id| { self.removeElementIdWithMaps(id_maps.?, id); } @@ -2480,7 +2480,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod // For main document parsing, we know nodes are connected (fast path) // For fragment parsing (innerHTML), we need to check connectivity if (child.isConnected() or child.isInShadowTree()) { - if (el.getAttributeSafe(comptime .literal("id"))) |id| { + if (el.getAttributeSafe(comptime .wrap("id"))) |id| { try self.addElementId(parent, el, id); } try Element.Html.Custom.invokeConnectedCallbackOnElement(true, el, self); @@ -2519,7 +2519,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(child, .{}); while (tw.next()) |el| { - if (el.getAttributeSafe(comptime .literal("id"))) |id| { + if (el.getAttributeSafe(comptime .wrap("id"))) |id| { try self.addElementId(el.asNode()._parent.?, el, id); } @@ -2529,7 +2529,7 @@ pub fn _insertNodeRelative(self: *Page, comptime from_parser: bool, parent: *Nod } } -pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value: []const u8, old_value: ?[]const u8) void { +pub fn attributeChange(self: *Page, element: *Element, name: String, value: String, old_value: ?String) void { _ = Element.Build.call(element, "attributeChange", .{ element, name, value, self }) catch |err| { log.err(.bug, "build.attributeChange", .{ .tag = element.getTag(), .name = name, .value = value, .err = err }); }; @@ -2545,9 +2545,9 @@ pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value: } // Handle slot assignment changes - if (std.mem.eql(u8, name, "slot")) { + if (name.eql(comptime .wrap("slot"))) { self.updateSlotAssignments(element); - } else if (std.mem.eql(u8, name, "name")) { + } else if (name.eql(comptime .wrap("name"))) { // Check if this is a slot element if (element.is(Element.Html.Slot)) |slot| { self.signalSlotChange(slot); @@ -2555,7 +2555,7 @@ pub fn attributeChange(self: *Page, element: *Element, name: []const u8, value: } } -pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_value: []const u8) void { +pub fn attributeRemove(self: *Page, element: *Element, name: String, old_value: String) void { _ = Element.Build.call(element, "attributeRemove", .{ element, name, self }) catch |err| { log.err(.bug, "build.attributeRemove", .{ .tag = element.getTag(), .name = name, .err = err }); }; @@ -2571,9 +2571,9 @@ pub fn attributeRemove(self: *Page, element: *Element, name: []const u8, old_val } // Handle slot assignment changes - if (std.mem.eql(u8, name, "slot")) { + if (name.eql(comptime .wrap("slot"))) { self.updateSlotAssignments(element); - } else if (std.mem.eql(u8, name, "name")) { + } else if (name.eql(comptime .wrap("name"))) { // Check if this is a slot element if (element.is(Element.Html.Slot)) |slot| { self.signalSlotChange(slot); @@ -2622,7 +2622,7 @@ fn updateElementAssignedSlot(self: *Page, element: *Element) void { const parent_el = parent.is(Element) orelse return; const shadow_root = self._element_shadow_roots.get(parent_el) orelse return; - const slot_name = element.getAttributeSafe(comptime .literal("slot")) orelse ""; + const slot_name = element.getAttributeSafe(comptime .wrap("slot")) orelse ""; // Recursively search through the shadow root for a matching slot if (findMatchingSlot(shadow_root.asNode(), slot_name)) |slot| { @@ -2919,7 +2919,7 @@ pub fn handleClick(self: *Page, target: *Node) !void { switch (html_element._type) { .anchor => |anchor| { - const href = element.getAttributeSafe(comptime .literal("href")) orelse return; + const href = element.getAttributeSafe(comptime .wrap("href")) orelse return; if (href.len == 0) { return; } @@ -2935,7 +2935,7 @@ pub fn handleClick(self: *Page, target: *Node) !void { return; } - if (try element.hasAttribute("download", self)) { + if (try element.hasAttribute(comptime .wrap("download"), self)) { log.warn(.browser, "a.download", .{}); return; } @@ -3015,7 +3015,7 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form const form = form_ orelse return; if (submitter_) |submitter| { - if (submitter.getAttributeSafe(comptime .literal("disabled")) != null) { + if (submitter.getAttributeSafe(comptime .wrap("disabled")) != null) { return; } } @@ -3028,13 +3028,13 @@ pub fn submitForm(self: *Page, submitter_: ?*Element, form_: ?*Element.Html.Form const transfer_arena = self._session.transfer_arena; - const encoding = form_element.getAttributeSafe(comptime .literal("enctype")); + const encoding = form_element.getAttributeSafe(comptime .wrap("enctype")); var buf = std.Io.Writer.Allocating.init(transfer_arena); try form_data.write(encoding, &buf.writer); - const method = form_element.getAttributeSafe(comptime .literal("method")) orelse ""; - var action = form_element.getAttributeSafe(comptime .literal("action")) orelse self.url; + const method = form_element.getAttributeSafe(comptime .wrap("method")) orelse ""; + var action = form_element.getAttributeSafe(comptime .wrap("action")) orelse self.url; var opts = NavigateOpts{ .reason = .form, diff --git a/src/browser/ScriptManager.zig b/src/browser/ScriptManager.zig index 6a86324d..53ffa10a 100644 --- a/src/browser/ScriptManager.zig +++ b/src/browser/ScriptManager.zig @@ -152,14 +152,14 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e script_element._executed = true; const element = script_element.asElement(); - if (element.getAttributeSafe(comptime .literal("nomodule")) != null) { + if (element.getAttributeSafe(comptime .wrap("nomodule")) != null) { // these scripts should only be loaded if we don't support modules // but since we do support modules, we can just skip them. return; } const kind: Script.Kind = blk: { - const script_type = element.getAttributeSafe(comptime .literal("type")) orelse break :blk .javascript; + const script_type = element.getAttributeSafe(comptime .wrap("type")) orelse break :blk .javascript; if (script_type.len == 0) { break :blk .javascript; } @@ -186,7 +186,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e var source: Script.Source = undefined; var remote_url: ?[:0]const u8 = null; const base_url = page.base(); - if (element.getAttributeSafe(comptime .literal("src"))) |src| { + if (element.getAttributeSafe(comptime .wrap("src"))) |src| { if (try parseDataURI(page.arena, src)) |data_uri| { source = .{ .@"inline" = data_uri }; } else { @@ -217,12 +217,12 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e break :blk if (kind == .module) .@"defer" else .normal; } - if (element.getAttributeSafe(comptime .literal("async")) != null) { + if (element.getAttributeSafe(comptime .wrap("async")) != null) { break :blk .async; } // Check for defer or module (before checking dynamic script default) - if (kind == .module or element.getAttributeSafe(comptime .literal("defer")) != null) { + if (kind == .module or element.getAttributeSafe(comptime .wrap("defer")) != null) { break :blk .@"defer"; } @@ -754,7 +754,7 @@ pub const Script = struct { return; } - switch (self.mode) { + switch (self.mode) { .import_async => |ia| ia.callback(ia.data, error.FailedToLoad), .import => { const entry = manager.imported_modules.getPtr(self.url).?; diff --git a/src/browser/dump.zig b/src/browser/dump.zig index 3d852146..a8b2bec6 100644 --- a/src/browser/dump.zig +++ b/src/browser/dump.zig @@ -66,7 +66,7 @@ pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: * if (opts.with_base) { const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode(); const base = try doc.createElement("base", null, page); - try base.setAttributeSafe("base", page.base(), page); + try base.setAttributeSafe(comptime .wrap("base"), .wrap(page.base()), page); _ = try parent.insertBefore(base.asNode(), parent.firstChild(), page); } } @@ -110,7 +110,7 @@ fn _deep(node: *Node, opts: Opts, comptime force_slot: bool, writer: *std.Io.Wri // to render that "active" content, so when we're trying to render // it, we don't want to skip it. if ((comptime force_slot == false) and opts.shadow == .rendered) { - if (el.getAttributeSafe(comptime .literal("slot"))) |_| { + if (el.getAttributeSafe(comptime .wrap("slot"))) |_| { // Skip - will be rendered by the Slot if it's the active container return; } @@ -253,12 +253,12 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool { if (std.mem.eql(u8, tag_name, "noscript")) return true; if (std.mem.eql(u8, tag_name, "link")) { - if (el.getAttributeSafe(comptime .literal("as"))) |as| { + if (el.getAttributeSafe(comptime .wrap("as"))) |as| { if (std.mem.eql(u8, as, "script")) return true; } - if (el.getAttributeSafe(comptime .literal("rel"))) |rel| { + if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| { if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) { - if (el.getAttributeSafe(comptime .literal("as"))) |as| { + if (el.getAttributeSafe(comptime .wrap("as"))) |as| { if (std.mem.eql(u8, as, "script")) return true; } } @@ -270,7 +270,7 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool { if (std.mem.eql(u8, tag_name, "style")) return true; if (std.mem.eql(u8, tag_name, "link")) { - if (el.getAttributeSafe(comptime .literal("rel"))) |rel| { + if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| { if (std.mem.eql(u8, rel, "stylesheet")) return true; } } diff --git a/src/browser/js/Caller.zig b/src/browser/js/Caller.zig index c3a38ed1..1eb160ba 100644 --- a/src/browser/js/Caller.zig +++ b/src/browser/js/Caller.zig @@ -18,6 +18,8 @@ const std = @import("std"); const log = @import("../../log.zig"); +const string = @import("../../string.zig"); + const Page = @import("../Page.zig"); const js = @import("js.zig"); @@ -219,7 +221,7 @@ fn _getNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v const F = @TypeOf(func); var args = try self.getArgs(F, 2, info); @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); - @field(args, "1") = try self.nameToString(name); + @field(args, "1") = try self.nameToString(@TypeOf(args.@"1"), name); const ret = @call(.auto, func, args); return self.handleIndexedReturn(T, F, true, ret, info, opts); } @@ -241,7 +243,7 @@ fn _setNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *const v const F = @TypeOf(func); var args: ParameterTypes(F) = undefined; @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); - @field(args, "1") = try self.nameToString(name); + @field(args, "1") = try self.nameToString(@TypeOf(args.@"1"), name); @field(args, "2") = try self.local.jsValueToZig(@TypeOf(@field(args, "2")), js_value); if (@typeInfo(F).@"fn".params.len == 4) { @field(args, "3") = self.local.ctx.page; @@ -266,7 +268,7 @@ fn _deleteNamedIndex(self: *Caller, comptime T: type, func: anytype, name: *cons const F = @TypeOf(func); var args: ParameterTypes(F) = undefined; @field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); - @field(args, "1") = try self.nameToString(name); + @field(args, "1") = try self.nameToString(@TypeOf(args.@"1"), name); if (@typeInfo(F).@"fn".params.len == 3) { @field(args, "2") = self.local.ctx.page; } @@ -311,8 +313,15 @@ fn isInErrorSet(err: anyerror, comptime T: type) bool { return false; } -fn nameToString(self: *const Caller, name: *const v8.Name) ![]const u8 { - return self.local.valueHandleToString(@ptrCast(name), .{}); +fn nameToString(self: *const Caller, comptime T: type, name: *const v8.Name) !T { + const v8_string = @as(*const v8.String, @ptrCast(name)); + if (T == string.String) { + return self.local.jsStringToStringSSO(v8_string, .{}); + } + if (T == string.Global) { + return self.local.jsStringToStringSSO(v8_string, .{ .allocator = self.local.ctx.allocator }); + } + return try self.local.valueHandleToString(v8_string, .{}); } fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void { diff --git a/src/browser/js/Local.zig b/src/browser/js/Local.zig index ffb1876d..c47a71a7 100644 --- a/src/browser/js/Local.zig +++ b/src/browser/js/Local.zig @@ -293,6 +293,10 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts) return js_obj.toValue(); } } + if (T == string.String or T == string.Global) { + // would have been handled by simpleZigValueToJs + unreachable; + } // zig fmt: off switch (T) { @@ -1154,11 +1158,15 @@ pub fn jsStringToStringSSO(self: *const Local, str: anytype, opts: ToStringOpts) const len: usize = @intCast(v8.v8__String__Utf8Length(handle, self.isolate.handle)); if (len <= 12) { - var content: [12]u8 = @splat(0); - const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, &content, len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); + var content: [12]u8 = undefined; + const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, &content[0], content.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8); if (comptime IS_DEBUG) { std.debug.assert(n == len); } + // Weird that we do this _after_, but we have to..I've seen weird issues + // in ReleaseMode where v8 won't write to content if it starts off zero + // initiated + @memset(content[len..], 0); return .{ .len = @intCast(len), .payload = .{ .content = content } }; } diff --git a/src/browser/js/js.zig b/src/browser/js/js.zig index e461a878..ee3c8009 100644 --- a/src/browser/js/js.zig +++ b/src/browser/js/js.zig @@ -20,6 +20,7 @@ const std = @import("std"); pub const v8 = @import("v8").c; const log = @import("../../log.zig"); +const string = @import("../../string.zig"); pub const Env = @import("Env.zig"); pub const bridge = @import("bridge.zig"); @@ -130,6 +131,7 @@ pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool, }, .@"struct" => { switch (@TypeOf(value)) { + string.String => return isolate.initStringHandle(value.str()), ArrayBuffer => { const values = value.values; const len = values.len; diff --git a/src/browser/tests/element/html/input.html b/src/browser/tests/element/html/input.html index b78c8185..2cc51a9f 100644 --- a/src/browser/tests/element/html/input.html +++ b/src/browser/tests/element/html/input.html @@ -183,7 +183,7 @@ } - --> diff --git a/src/browser/webapi/CustomElementDefinition.zig b/src/browser/webapi/CustomElementDefinition.zig index 41da64cb..de802f12 100644 --- a/src/browser/webapi/CustomElementDefinition.zig +++ b/src/browser/webapi/CustomElementDefinition.zig @@ -17,6 +17,8 @@ // along with this program. If not, see . const std = @import("std"); +const String = @import("../../string.zig").String; + const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); const Element = @import("Element.zig"); @@ -25,13 +27,16 @@ const CustomElementDefinition = @This(); name: []const u8, constructor: js.Function.Global, + +// TODO: Make this a Map observed_attributes: std.StringHashMapUnmanaged(void) = .{}, + // For customized built-in elements, this is the element tag they extend (e.g., .button) // For autonomous custom elements, this is null extends: ?Element.Tag = null, -pub fn isAttributeObserved(self: *const CustomElementDefinition, name: []const u8) bool { - return self.observed_attributes.contains(name); +pub fn isAttributeObserved(self: *const CustomElementDefinition, name: String) bool { + return self.observed_attributes.contains(name.str()); } pub fn isAutonomous(self: *const CustomElementDefinition) bool { diff --git a/src/browser/webapi/CustomElementRegistry.zig b/src/browser/webapi/CustomElementRegistry.zig index d69ad542..461e7c6a 100644 --- a/src/browser/webapi/CustomElementRegistry.zig +++ b/src/browser/webapi/CustomElementRegistry.zig @@ -188,9 +188,9 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio // Invoke attributeChangedCallback for existing observed attributes var attr_it = custom.asElement().attributeIterator(); while (attr_it.next()) |attr| { - const name = attr._name.str(); + const name = attr._name; if (definition.isAttributeObserved(name)) { - custom.invokeAttributeChangedCallback(name, null, attr._value.str(), page); + custom.invokeAttributeChangedCallback(name, null, attr._value, page); } } diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index e3b11987..e5c5f623 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -144,7 +144,7 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement const options = options_ orelse return element; if (options.is) |is_value| { - try element.setAttribute("is", is_value, page); + try element.setAttribute(comptime .wrap("is"), .wrap(is_value), page); try Element.Html.Custom.checkAndAttachBuiltIn(element, page); } @@ -162,26 +162,26 @@ pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8 return node.as(Element); } -pub fn createAttribute(_: *const Document, name: []const u8, page: *Page) !?*Element.Attribute { - try Element.Attribute.validateAttributeName(name); +pub fn createAttribute(_: *const Document, name: String.Global, page: *Page) !?*Element.Attribute { + try Element.Attribute.validateAttributeName(name.str); return page._factory.node(Element.Attribute{ ._proto = undefined, - ._name = try page.dupeString(name), - ._value = "", + ._name = name.str, + ._value = String.empty, ._element = null, }); } -pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: []const u8, page: *Page) !?*Element.Attribute { +pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: String.Global, page: *Page) !?*Element.Attribute { if (std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml") == false) { log.warn(.not_implemented, "document.createAttributeNS", .{ .namespace = namespace }); } - try Element.Attribute.validateAttributeName(name); + try Element.Attribute.validateAttributeName(name.str); return page._factory.node(Element.Attribute{ ._proto = undefined, - ._name = try page.dupeString(name), - ._value = "", + ._name = name.str, + ._value = String.empty, ._element = null, }); } @@ -199,7 +199,7 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*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 .literal("id")) orelse continue; + const element_id = el.getAttributeSafe(comptime .wrap("id")) 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 diff --git a/src/browser/webapi/DocumentFragment.zig b/src/browser/webapi/DocumentFragment.zig index 3f933fdc..74099104 100644 --- a/src/browser/webapi/DocumentFragment.zig +++ b/src/browser/webapi/DocumentFragment.zig @@ -74,7 +74,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 .literal("id"))) |element_id| { + if (el.getAttributeSafe(comptime .wrap("id"))) |element_id| { if (std.mem.eql(u8, element_id, id)) { return el; } diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 456369cf..f983b514 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -427,35 +427,35 @@ pub fn setInnerHTML(self: *Element, html: []const u8, page: *Page) !void { } pub fn getId(self: *const Element) []const u8 { - return self.getAttributeSafe(comptime .literal("id")) orelse ""; + return self.getAttributeSafe(comptime .wrap("id")) orelse ""; } pub fn setId(self: *Element, value: []const u8, page: *Page) !void { - return self.setAttributeSafe("id", value, page); + return self.setAttributeSafe(comptime .wrap("id"), .wrap(value), page); } pub fn getSlot(self: *const Element) []const u8 { - return self.getAttributeSafe(comptime .literal("slot")) orelse ""; + return self.getAttributeSafe(comptime .wrap("slot")) orelse ""; } pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void { - return self.setAttributeSafe("slot", value, page); + return self.setAttributeSafe(comptime .wrap("slot"), .wrap(value), page); } pub fn getDir(self: *const Element) []const u8 { - return self.getAttributeSafe(comptime .literal("dir")) orelse ""; + return self.getAttributeSafe(comptime .wrap("dir")) orelse ""; } pub fn setDir(self: *Element, value: []const u8, page: *Page) !void { - return self.setAttributeSafe("dir", value, page); + return self.setAttributeSafe(comptime .wrap("dir"), .wrap(value), page); } pub fn getClassName(self: *const Element) []const u8 { - return self.getAttributeSafe(comptime .literal("class")) orelse ""; + return self.getAttributeSafe(comptime .wrap("class")) orelse ""; } pub fn setClassName(self: *Element, value: []const u8, page: *Page) !void { - return self.setAttributeSafe("class", value, page); + return self.setAttributeSafe(comptime .wrap("class"), .wrap(value), page); } pub fn attributeIterator(self: *Element) Attribute.InnerIterator { @@ -463,7 +463,7 @@ pub fn attributeIterator(self: *Element) Attribute.InnerIterator { return attributes.iterator(); } -pub fn getAttribute(self: *const Element, name: []const u8, page: *Page) !?[]const u8 { +pub fn getAttribute(self: *const Element, name: String, page: *Page) !?String { const attributes = self._attributes orelse return null; return attributes.get(name, page); } @@ -472,9 +472,9 @@ pub fn getAttribute(self: *const Element, name: []const u8, page: *Page) !?[]con pub fn getAttributeNS( self: *const Element, maybe_namespace: ?[]const u8, - local_name: []const u8, + local_name: String, page: *Page, -) !?[]const u8 { +) !?String { if (maybe_namespace) |namespace| { if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) { log.warn(.not_implemented, "Element.getAttributeNS", .{ .namespace = namespace }); @@ -489,7 +489,7 @@ pub fn getAttributeSafe(self: *const Element, name: String) ?[]const u8 { return attributes.getSafe(name); } -pub fn hasAttribute(self: *const Element, name: []const u8, page: *Page) !bool { +pub fn hasAttribute(self: *const Element, name: String, page: *Page) !bool { const attributes = self._attributes orelse return false; const value = try attributes.get(name, page); return value != null; @@ -505,12 +505,12 @@ pub fn hasAttributes(self: *const Element) bool { return attributes.isEmpty() == false; } -pub fn getAttributeNode(self: *Element, name: []const u8, page: *Page) !?*Attribute { +pub fn getAttributeNode(self: *Element, name: String, page: *Page) !?*Attribute { const attributes = self._attributes orelse return null; return attributes.getAttribute(name, self, page); } -pub fn setAttribute(self: *Element, name: []const u8, value: []const u8, page: *Page) !void { +pub fn setAttribute(self: *Element, name: String, value: String, page: *Page) !void { try Attribute.validateAttributeName(name); const attributes = try self.getOrCreateAttributeList(page); _ = try attributes.put(name, value, self, page); @@ -533,10 +533,10 @@ pub fn setAttributeNS( qualified_name[idx + 1 ..] else qualified_name; - return self.setAttribute(local_name, value, page); + return self.setAttribute(.wrap(local_name), .wrap(value), page); } -pub fn setAttributeSafe(self: *Element, name: []const u8, value: []const u8, page: *Page) !void { +pub fn setAttributeSafe(self: *Element, name: String, value: String, page: *Page) !void { const attributes = try self.getOrCreateAttributeList(page); _ = try attributes.putSafe(name, value, self, page); } @@ -607,19 +607,19 @@ pub fn setAttributeNode(self: *Element, attr: *Attribute, page: *Page) !?*Attrib return attributes.putAttribute(attr, self, page); } -pub fn removeAttribute(self: *Element, name: []const u8, page: *Page) !void { +pub fn removeAttribute(self: *Element, name: String, page: *Page) !void { const attributes = self._attributes orelse return; return attributes.delete(name, self, page); } -pub fn toggleAttribute(self: *Element, name: []const u8, force: ?bool, page: *Page) !bool { +pub fn toggleAttribute(self: *Element, name: String, force: ?bool, page: *Page) !bool { try Attribute.validateAttributeName(name); const has = try self.hasAttribute(name, page); const should_add = force orelse !has; if (should_add and !has) { - try self.setAttribute(name, "", page); + try self.setAttribute(name, String.empty, page); return true; } else if (!should_add and has) { try self.removeAttribute(name, page); @@ -666,7 +666,7 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList { if (!gop.found_existing) { gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{ ._element = self, - ._attribute_name = comptime .literal("class"), + ._attribute_name = comptime .wrap("class"), }); } return gop.value_ptr.*; @@ -677,7 +677,7 @@ pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList { if (!gop.found_existing) { gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{ ._element = self, - ._attribute_name = comptime .literal("rel"), + ._attribute_name = comptime .wrap("rel"), }); } return gop.value_ptr.*; @@ -919,10 +919,10 @@ fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, heigh if (width == 5.0) width = 1920.0; if (height == 5.0) height = 100_000_000.0; } else if (tag == .img or tag == .iframe) { - if (self.getAttributeSafe(comptime .literal("width"))) |w| { + if (self.getAttributeSafe(comptime .wrap("width"))) |w| { width = std.fmt.parseFloat(f64, w) catch width; } - if (self.getAttributeSafe(comptime .literal("height"))) |h| { + if (self.getAttributeSafe(comptime .wrap("height"))) |h| { height = std.fmt.parseFloat(f64, h) catch height; } } diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 20a1ea7d..8d01cb42 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -17,6 +17,8 @@ // along with this program. If not, see . const std = @import("std"); +const String = @import("../../string.zig").String; + const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); const Node = @import("Node.zig"); @@ -109,8 +111,8 @@ pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord { pub fn notifyAttributeChange( self: *MutationObserver, target: *Element, - attribute_name: []const u8, - old_value: ?[]const u8, + attribute_name: String, + old_value: ?String, page: *Page, ) !void { const target_node = target.asNode(); @@ -129,7 +131,7 @@ pub fn notifyAttributeChange( } if (obs.options.attributeFilter) |filter| { for (filter) |name| { - if (std.mem.eql(u8, name, attribute_name)) { + if (attribute_name.eqlSlice(name)) { break; } } else { @@ -140,9 +142,9 @@ pub fn notifyAttributeChange( const record = try page._factory.create(MutationRecord{ ._type = .attributes, ._target = target_node, - ._attribute_name = try page.arena.dupe(u8, attribute_name), + ._attribute_name = try page.arena.dupe(u8, attribute_name.str()), ._old_value = if (obs.options.attributeOldValue and old_value != null) - try page.arena.dupe(u8, old_value.?) + try page.arena.dupe(u8, old_value.?.str()) else null, ._added_nodes = &.{}, diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 295d957b..8948c999 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -17,8 +17,9 @@ // along with this program. If not, see . const std = @import("std"); - const log = @import("../../log.zig"); +const String = @import("../../string.zig").String; + const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); const reflect = @import("../reflect.zig"); @@ -268,7 +269,7 @@ pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!vo .document => {}, .document_type => {}, .document_fragment => {}, - .attribute => |attr| try writer.writeAll(attr._value), + .attribute => |attr| try writer.writeAll(attr._value.str()), } } @@ -297,7 +298,7 @@ pub fn setTextContent(self: *Node, data: []const u8, page: *Page) !void { } return frag.replaceChildren(&.{.{ .text = data }}, page); }, - .attribute => |attr| return attr.setValue(data, page), + .attribute => |attr| return attr.setValue(.wrap(data), page), } } @@ -313,7 +314,7 @@ pub fn getNodeName(self: *const Node, buf: []u8) []const u8 { .document => "#document", .document_type => |dt| dt.getName(), .document_fragment => "#document-fragment", - .attribute => |attr| attr._name, + .attribute => |attr| attr._name.str(), }; } @@ -559,7 +560,7 @@ pub fn replaceChild(self: *Node, new_child: *Node, old_child: *Node, page: *Page pub fn getNodeValue(self: *const Node) ?[]const u8 { return switch (self._type) { .cdata => |c| c.getData(), - .attribute => |attr| attr._value, + .attribute => |attr| attr._value.str(), .element => null, .document => null, .document_type => null, @@ -567,9 +568,9 @@ pub fn getNodeValue(self: *const Node) ?[]const u8 { }; } -pub fn setNodeValue(self: *const Node, value: ?[]const u8, page: *Page) !void { +pub fn setNodeValue(self: *const Node, value: ?String, page: *Page) !void { switch (self._type) { - .cdata => |c| try c.setData(value, page), + .cdata => |c| try c.setData(if (value) |v| v.str() else null, page), .attribute => |attr| try attr.setValue(value, page), .element => {}, .document => {}, @@ -910,7 +911,7 @@ pub const JsApi = struct { return buf.written(); }, .cdata => |cdata| return cdata.getData(), - .attribute => |attr| return attr._value, + .attribute => |attr| return attr._value.str(), .document => return null, .document_type => return null, .document_fragment => return null, diff --git a/src/browser/webapi/ShadowRoot.zig b/src/browser/webapi/ShadowRoot.zig index 25048913..09bfa2d9 100644 --- a/src/browser/webapi/ShadowRoot.zig +++ b/src/browser/webapi/ShadowRoot.zig @@ -84,7 +84,7 @@ pub fn getElementById(self: *ShadowRoot, id: []const u8, page: *Page) ?*Element // Do a tree walk to find another element with this ID var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{}); while (tw.next()) |el| { - const element_id = el.getAttributeSafe(comptime .literal("id")) orelse continue; + const element_id = el.getAttributeSafe(comptime .wrap("id")) 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 diff --git a/src/browser/webapi/collections/DOMTokenList.zig b/src/browser/webapi/collections/DOMTokenList.zig index 8506175a..a9f31812 100644 --- a/src/browser/webapi/collections/DOMTokenList.zig +++ b/src/browser/webapi/collections/DOMTokenList.zig @@ -160,8 +160,8 @@ pub fn getValue(self: *const DOMTokenList) []const u8 { return self._element.getAttributeSafe(self._attribute_name) orelse ""; } -pub fn setValue(self: *DOMTokenList, value: []const u8, page: *Page) !void { - try self._element.setAttribute(self._attribute_name.str(), value, page); +pub fn setValue(self: *DOMTokenList, value: String, page: *Page) !void { + try self._element.setAttribute(self._attribute_name, value, page); } pub fn keys(self: *DOMTokenList, page: *Page) !*KeyIterator { @@ -227,7 +227,7 @@ fn validateToken(token: []const u8) !void { fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void { const joined = try std.mem.join(page.call_arena, " ", tokens.keys()); - try self._element.setAttribute(self._attribute_name.str(), joined, page); + try self._element.setAttribute(self._attribute_name, .wrap(joined), page); } const Iterator = struct { diff --git a/src/browser/webapi/collections/HTMLAllCollection.zig b/src/browser/webapi/collections/HTMLAllCollection.zig index facb856a..32fb3056 100644 --- a/src/browser/webapi/collections/HTMLAllCollection.zig +++ b/src/browser/webapi/collections/HTMLAllCollection.zig @@ -111,7 +111,7 @@ pub fn getByName(self: *HTMLAllCollection, name: []const u8, page: *Page) ?*Elem while (tw.next()) |node| { if (node.is(Element)) |el| { - if (el.getAttributeSafe(comptime .literal("name"))) |attr_name| { + if (el.getAttributeSafe(comptime .wrap("name"))) |attr_name| { if (std.mem.eql(u8, attr_name, name)) { return el; } diff --git a/src/browser/webapi/collections/HTMLFormControlsCollection.zig b/src/browser/webapi/collections/HTMLFormControlsCollection.zig index e7f06a14..20f9210b 100644 --- a/src/browser/webapi/collections/HTMLFormControlsCollection.zig +++ b/src/browser/webapi/collections/HTMLFormControlsCollection.zig @@ -59,12 +59,12 @@ pub fn namedItem(self: *HTMLFormControlsCollection, name: []const u8, page: *Pag var it = try self.iterator(); while (it.next()) |element| { const is_match = blk: { - if (element.getAttributeSafe(comptime .literal("id"))) |id| { + if (element.getAttributeSafe(comptime .wrap("id"))) |id| { if (std.mem.eql(u8, id, name)) { break :blk true; } } - if (element.getAttributeSafe(comptime .literal("name"))) |elem_name| { + if (element.getAttributeSafe(comptime .wrap("name"))) |elem_name| { if (std.mem.eql(u8, elem_name, name)) { break :blk true; } diff --git a/src/browser/webapi/collections/RadioNodeList.zig b/src/browser/webapi/collections/RadioNodeList.zig index fc5347c3..f236f3a7 100644 --- a/src/browser/webapi/collections/RadioNodeList.zig +++ b/src/browser/webapi/collections/RadioNodeList.zig @@ -69,7 +69,7 @@ pub fn getValue(self: *RadioNodeList) ![]const u8 { if (!input.getChecked()) { continue; } - return element.getAttributeSafe(comptime .literal("value")) orelse "on"; + return element.getAttributeSafe(comptime .wrap("value")) orelse "on"; } return ""; } @@ -82,7 +82,7 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void { continue; } - const input_value = element.getAttributeSafe(comptime .literal("value")); + const input_value = element.getAttributeSafe(comptime .wrap("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")); @@ -99,12 +99,12 @@ pub fn setValue(self: *RadioNodeList, value: []const u8, page: *Page) !void { } fn matches(self: *const RadioNodeList, element: *Element) bool { - if (element.getAttributeSafe(comptime .literal("id"))) |id| { + if (element.getAttributeSafe(comptime .wrap("id"))) |id| { if (std.mem.eql(u8, id, self._name)) { return true; } } - if (element.getAttributeSafe(comptime .literal("name"))) |elem_name| { + if (element.getAttributeSafe(comptime .wrap("name"))) |elem_name| { if (std.mem.eql(u8, elem_name, self._name)) { return true; } diff --git a/src/browser/webapi/collections/node_live.zig b/src/browser/webapi/collections/node_live.zig index 3d28ad07..afe7a3f4 100644 --- a/src/browser/webapi/collections/node_live.zig +++ b/src/browser/webapi/collections/node_live.zig @@ -187,7 +187,7 @@ pub fn NodeLive(comptime mode: Mode) type { // (like length or getAtIndex) var tw = self._tw.clone(); while (self.nextTw(&tw)) |element| { - const element_name = element.getAttributeSafe(comptime .literal("name")) orelse continue; + const element_name = element.getAttributeSafe(comptime .wrap("name")) orelse continue; if (std.mem.eql(u8, element_name, name)) { return element; } @@ -228,7 +228,7 @@ pub fn NodeLive(comptime mode: Mode) type { } const el = node.is(Element) orelse return false; - const class_attr = el.getAttributeSafe(comptime .literal("class")) orelse return false; + const class_attr = el.getAttributeSafe(comptime .wrap("class")) orelse return false; for (self._filter) |class_name| { if (!Selector.classAttributeContains(class_attr, class_name)) { return false; @@ -238,7 +238,7 @@ pub fn NodeLive(comptime mode: Mode) type { }, .name => { const el = node.is(Element) orelse return false; - const name_attr = el.getAttributeSafe(comptime .literal("name")) orelse return false; + const name_attr = el.getAttributeSafe(comptime .wrap("name")) orelse return false; return std.mem.eql(u8, name_attr, self._filter); }, .all_elements => return node._type == .element, @@ -258,14 +258,14 @@ pub fn NodeLive(comptime mode: Mode) type { const el = node.is(Element) orelse return false; const Anchor = Element.Html.Anchor; if (el.is(Anchor) == null) return false; - return el.hasAttributeSafe(comptime .literal("href")); + return el.hasAttributeSafe(comptime .wrap("href")); }, .anchors => { // Anchors are 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 .literal("name")); + return el.hasAttributeSafe(comptime .wrap("name")); }, .form => { const el = node.is(Element) orelse return false; @@ -273,8 +273,8 @@ pub fn NodeLive(comptime mode: Mode) type { return false; } - if (el.getAttributeSafe(comptime .literal("form"))) |form_attr| { - const form_id = self._filter.asElement().getAttributeSafe(comptime .literal("id")) orelse return false; + if (el.getAttributeSafe(comptime .wrap("form"))) |form_attr| { + const form_id = self._filter.asElement().getAttributeSafe(comptime .wrap("id")) orelse return false; return std.mem.eql(u8, form_attr, form_id); } diff --git a/src/browser/webapi/element/Attribute.zig b/src/browser/webapi/element/Attribute.zig index a913457a..5a86454d 100644 --- a/src/browser/webapi/element/Attribute.zig +++ b/src/browser/webapi/element/Attribute.zig @@ -39,33 +39,33 @@ pub fn registerTypes() []const type { pub const Attribute = @This(); _proto: *Node, -_name: []const u8, -_value: []const u8, +_name: String, +_value: String, _element: ?*Element, pub fn format(self: *const Attribute, writer: *std.Io.Writer) !void { return formatAttribute(self._name, self._value, writer); } -pub fn getName(self: *const Attribute) []const u8 { +pub fn getName(self: *const Attribute) String { return self._name; } -pub fn getValue(self: *const Attribute) []const u8 { +pub fn getValue(self: *const Attribute) String { return self._value; } -pub fn setValue(self: *Attribute, data_: ?[]const u8, page: *Page) !void { - const data = data_ orelse ""; +pub fn setValue(self: *Attribute, data_: ?String, page: *Page) !void { + const data = data_ orelse String.empty; const el = self._element orelse { - self._value = try page.dupeString(data); + self._value = try data.dupe(page.arena); return; }; // this takes ownership of the data try el.setAttribute(self._name, data, page); // not the most efficient, but we don't expect this to be called often - self._value = (try el.getAttribute(self._name, page)) orelse ""; + self._value = (try el.getAttribute(self._name, page)) orelse String.empty; } pub fn getNamespaceURI(_: *const Attribute) ?[]const u8 { @@ -77,7 +77,7 @@ pub fn getOwnerElement(self: *const Attribute) ?*Element { } pub fn isEqualNode(self: *const Attribute, other: *const Attribute) bool { - return std.mem.eql(u8, self.getName(), other.getName()) and std.mem.eql(u8, self.getValue(), other.getValue()); + return self.getName().eql(other.getName()) and self.getValue().eql(other.getValue()); } pub fn clone(self: *const Attribute, page: *Page) !*Attribute { @@ -139,9 +139,9 @@ pub const List = struct { return self._list.first == null; } - pub fn get(self: *const List, name: []const u8, page: *Page) !?[]const u8 { + pub fn get(self: *const List, name: String, page: *Page) !?String { const entry = (try self.getEntry(name, page)) orelse return null; - return entry._value.str(); + return entry._value; } pub inline fn length(self: *const List) usize { @@ -180,7 +180,7 @@ pub const List = struct { return self.getEntryWithNormalizedName(name) != null; } - pub fn getAttribute(self: *const List, name: []const u8, element: ?*Element, page: *Page) !?*Attribute { + pub fn getAttribute(self: *const List, name: String, element: ?*Element, page: *Page) !?*Attribute { const entry = (try self.getEntry(name, page)) orelse return null; const gop = try page._attribute_lookup.getOrPut(page.arena, @intFromPtr(entry)); if (gop.found_existing) { @@ -191,33 +191,33 @@ pub const List = struct { return attribute; } - pub fn put(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry { + pub fn put(self: *List, name: String, value: String, element: *Element, page: *Page) !*Entry { const result = try self.getEntryAndNormalizedName(name, page); return self._put(result, value, element, page); } - pub fn putSafe(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry { - const entry = self.getEntryWithNormalizedNameOld(name); + pub fn putSafe(self: *List, name: String, value: String, element: *Element, page: *Page) !*Entry { + const entry = self.getEntryWithNormalizedName(name); return self._put(.{ .entry = entry, .normalized = name }, value, element, page); } - fn _put(self: *List, result: NormalizeAndEntry, value: []const u8, element: *Element, page: *Page) !*Entry { + fn _put(self: *List, result: NormalizeAndEntry, value: String, element: *Element, page: *Page) !*Entry { const is_id = shouldAddToIdMap(result.normalized, element); var entry: *Entry = undefined; - var old_value: ?[]const u8 = null; + var old_value: ?String = null; if (result.entry) |e| { - old_value = try page.call_arena.dupe(u8, e._value.str()); + old_value = try e._value.dupe(page.call_arena); if (is_id) { page.removeElementId(element, e._value.str()); } - e._value = try String.init(page.arena, value, .{}); + e._value = try value.dupe(page.arena); entry = e; } else { entry = try page._factory.create(Entry{ ._node = .{}, - ._name = try String.init(page.arena, result.normalized, .{}), - ._value = try String.init(page.arena, value, .{}), + ._name = try result.normalized.dupe(page.arena), + ._value = try value.dupe(page.arena), }); self._list.append(&entry._node); self._len += 1; @@ -230,7 +230,7 @@ pub const List = struct { try page.addElementId(parent, element, entry._value.str()); } page.domChanged(); - page.attributeChange(element, result.normalized, entry._value.str(), old_value); + page.attributeChange(element, result.normalized, entry._value, old_value); return entry; } @@ -266,7 +266,7 @@ pub const List = struct { // called form our parser, names already lower-cased pub fn putNew(self: *List, name: []const u8, value: []const u8, page: *Page) !void { - if (try self.getEntry(name, page) != null) { + if (try self.getEntry(.wrap(name), page) != null) { // When parsing, if there are dupicate names, it isn't valid, and // the first is kept return; @@ -281,12 +281,12 @@ pub const List = struct { self._len += 1; } - pub fn delete(self: *List, name: []const u8, element: *Element, page: *Page) !void { + pub fn delete(self: *List, name: String, element: *Element, page: *Page) !void { const result = try self.getEntryAndNormalizedName(name, page); const entry = result.entry orelse return; const is_id = shouldAddToIdMap(result.normalized, element); - const old_value = entry._value.str(); + const old_value = entry._value; if (is_id) { page.removeElementId(element, entry._value.str()); @@ -314,7 +314,7 @@ pub const List = struct { return .{ ._node = self._list.first }; } - fn getEntry(self: *const List, name: []const u8, page: *Page) !?*Entry { + fn getEntry(self: *const List, name: String, page: *Page) !?*Entry { const result = try self.getEntryAndNormalizedName(name, page); return result.entry; } @@ -322,16 +322,16 @@ pub const List = struct { // Dangerous, the returned normalized name is only valid until someone // else uses pages.buf. const NormalizeAndEntry = struct { - normalized: []const u8, entry: ?*Entry, + normalized: String, }; - fn getEntryAndNormalizedName(self: *const List, name: []const u8, page: *Page) !NormalizeAndEntry { + fn getEntryAndNormalizedName(self: *const List, name: String, page: *Page) !NormalizeAndEntry { const normalized = if (self.normalize) try normalizeNameForLookup(name, page) else name; return .{ .normalized = normalized, - .entry = self.getEntryWithNormalizedNameOld(normalized), + .entry = self.getEntryWithNormalizedName(normalized), }; } @@ -347,19 +347,6 @@ pub const List = struct { return null; } - // TODO remove when we're done making everything String-based - fn getEntryWithNormalizedNameOld(self: *const List, name: []const u8) ?*Entry { - var node = self._list.first; - while (node) |n| { - var e = Entry.fromNode(n); - if (e._name.eqlSlice(name)) { - return e; - } - node = n.next; - } - return null; - } - pub const Entry = struct { _name: String, _value: String, @@ -376,7 +363,7 @@ pub const List = struct { } pub fn format(self: *const Entry, writer: *std.Io.Writer) !void { - return formatAttribute(self._name.str(), self._value.str(), writer); + return formatAttribute(self._name, self._value, writer); } pub fn toAttribute(self: *const Entry, element: ?*Element, page: *Page) !*Attribute { @@ -386,15 +373,15 @@ pub const List = struct { // Cannot directly reference self._name.str() and self._value.str() // This attribute can outlive the list entry (the node can be // removed from the element's attribute, but still exist in the DOM) - ._name = try page.dupeString(self._name.str()), - ._value = try page.dupeString(self._value.str()), + ._name = try self._name.dupe(page.arena), + ._value = try self._value.dupe(page.arena), }); } }; }; -fn shouldAddToIdMap(normalized_name: []const u8, element: *Element) bool { - if (!std.mem.eql(u8, normalized_name, "id")) { +fn shouldAddToIdMap(normalized_name: String, element: *Element) bool { + if (!normalized_name.eql(comptime .wrap("id"))) { return false; } @@ -407,17 +394,19 @@ fn shouldAddToIdMap(normalized_name: []const u8, element: *Element) bool { return node.isConnected(); } -pub fn validateAttributeName(name: []const u8) !void { - if (name.len == 0) { +pub fn validateAttributeName(name: String) !void { + const name_str = name.str(); + + if (name_str.len == 0) { return error.InvalidCharacterError; } - const first = name[0]; + const first = name_str[0]; if ((first >= '0' and first <= '9') or first == '-' or first == '.') { return error.InvalidCharacterError; } - for (name) |c| { + for (name_str) |c| { if (c == 0 or c == '/' or c == '=' or c == '>' or std.ascii.isWhitespace(c)) { return error.InvalidCharacterError; } @@ -433,14 +422,16 @@ pub fn validateAttributeName(name: []const u8) !void { } } -pub fn normalizeNameForLookup(name: []const u8, page: *Page) ![]const u8 { - if (!needsLowerCasing(name)) { +pub fn normalizeNameForLookup(name: String, page: *Page) !String { + if (!needsLowerCasing(name.str())) { return name; } - if (name.len < page.buf.len) { - return std.ascii.lowerString(&page.buf, name); - } - return std.ascii.allocLowerString(page.call_arena, name); + const normalized = if (name.len < page.buf.len) + std.ascii.lowerString(&page.buf, name.str()) + else + try std.ascii.allocLowerString(page.call_arena, name.str()); + + return .wrap(normalized); } fn needsLowerCasing(name: []const u8) bool { @@ -494,7 +485,7 @@ pub const NamedNodeMap = struct { return null; } - pub fn getByName(self: *const NamedNodeMap, name: []const u8, page: *Page) !?*Attribute { + pub fn getByName(self: *const NamedNodeMap, name: String, page: *Page) !?*Attribute { return self._list.getAttribute(name, self._element, page); } @@ -503,7 +494,7 @@ pub const NamedNodeMap = struct { return self._list.putAttribute(attribute, self._element, page); } - pub fn removeByName(self: *const NamedNodeMap, name: []const u8, page: *Page) !?*Attribute { + pub fn removeByName(self: *const NamedNodeMap, name: String, page: *Page) !?*Attribute { // this 2-step process (get then delete) isn't efficient. But we don't // expect this to be called often, and this lets us keep delete straightforward. const attr = (try self.getByName(name, page)) orelse return null; @@ -569,11 +560,13 @@ pub const InnerIterator = struct { } }; -fn formatAttribute(name: []const u8, value: []const u8, writer: *std.Io.Writer) !void { - try writer.writeAll(name); +fn formatAttribute(name: String, value_: String, writer: *std.Io.Writer) !void { + try writer.writeAll(name.str()); // Boolean attributes with empty values are serialized without a value - if (value.len == 0 and boolean_attributes_lookup.has(name)) { + + const value = value_.str(); + if (value.len == 0 and boolean_attributes_lookup.has(name.str())) { return; } diff --git a/src/browser/webapi/element/DOMStringMap.zig b/src/browser/webapi/element/DOMStringMap.zig index 4d1d2307..5592a193 100644 --- a/src/browser/webapi/element/DOMStringMap.zig +++ b/src/browser/webapi/element/DOMStringMap.zig @@ -21,6 +21,7 @@ const js = @import("../../js/js.zig"); const Element = @import("../Element.zig"); const Page = @import("../../Page.zig"); +const String = @import("../../../string.zig").String; const Allocator = std.mem.Allocator; @@ -28,28 +29,60 @@ const DOMStringMap = @This(); _element: *Element, -fn getProperty(self: *DOMStringMap, name: []const u8, page: *Page) !?[]const u8 { +fn getProperty(self: *DOMStringMap, name: String, page: *Page) !?String { const attr_name = try camelToKebab(page.call_arena, name); return try self._element.getAttribute(attr_name, page); } -fn setProperty(self: *DOMStringMap, name: []const u8, value: []const u8, page: *Page) !void { +fn setProperty(self: *DOMStringMap, name: String, value: String, page: *Page) !void { const attr_name = try camelToKebab(page.call_arena, name); return self._element.setAttributeSafe(attr_name, value, page); } -fn deleteProperty(self: *DOMStringMap, name: []const u8, page: *Page) !void { +fn deleteProperty(self: *DOMStringMap, name: String, page: *Page) !void { const attr_name = try camelToKebab(page.call_arena, name); try self._element.removeAttribute(attr_name, page); } -// fooBar -> foo-bar -fn camelToKebab(arena: Allocator, camel: []const u8) ![]const u8 { +// fooBar -> data-foo-bar (with SSO optimization for short strings) +fn camelToKebab(arena: Allocator, camel: String) !String { + const camel_str = camel.str(); + + // Calculate output length + var output_len: usize = 5; // "data-" + for (camel_str, 0..) |c, i| { + output_len += 1; + if (std.ascii.isUpper(c) and i > 0) output_len += 1; // extra char for '-' + } + + if (output_len <= 12) { + // SSO path - no allocation! + var content: [12]u8 = @splat(0); + @memcpy(content[0..5], "data-"); + var idx: usize = 5; + + for (camel_str, 0..) |c, i| { + if (std.ascii.isUpper(c)) { + if (i > 0) { + content[idx] = '-'; + idx += 1; + } + content[idx] = std.ascii.toLower(c); + } else { + content[idx] = c; + } + idx += 1; + } + + return .{ .len = @intCast(output_len), .payload = .{ .content = content } }; + } + + // Fallback: allocate for longer strings var result: std.ArrayList(u8) = .empty; - try result.ensureTotalCapacity(arena, 5 + camel.len * 2); + try result.ensureTotalCapacity(arena, output_len); result.appendSliceAssumeCapacity("data-"); - for (camel, 0..) |c, i| { + for (camel_str, 0..) |c, i| { if (std.ascii.isUpper(c)) { if (i > 0) { result.appendAssumeCapacity('-'); @@ -60,7 +93,7 @@ fn camelToKebab(arena: Allocator, camel: []const u8) ![]const u8 { } } - return result.items; + return try String.init(arena, result.items, .{}); } // data-foo-bar -> fooBar diff --git a/src/browser/webapi/element/Html.zig b/src/browser/webapi/element/Html.zig index 2411e711..9eb41edd 100644 --- a/src/browser/webapi/element/Html.zig +++ b/src/browser/webapi/element/Html.zig @@ -243,7 +243,7 @@ fn _getInnerText(self: *HtmlElement, writer: *std.Io.Writer, state: *innerTextSt .document => {}, .document_type => {}, .document_fragment => {}, - .attribute => |attr| try writer.writeAll(attr._value), + .attribute => |attr| try writer.writeAll(attr._value.str()), } } } diff --git a/src/browser/webapi/element/html/Anchor.zig b/src/browser/webapi/element/html/Anchor.zig index ec216c39..3adb3741 100644 --- a/src/browser/webapi/element/html/Anchor.zig +++ b/src/browser/webapi/element/html/Anchor.zig @@ -40,7 +40,7 @@ pub fn asNode(self: *Anchor) *Node { pub fn getHref(self: *Anchor, page: *Page) ![]const u8 { const element = self.asElement(); - const href = element.getAttributeSafe(comptime .literal("href")) orelse return ""; + const href = element.getAttributeSafe(comptime .wrap("href")) orelse return ""; if (href.len == 0) { return ""; } @@ -48,15 +48,15 @@ pub fn getHref(self: *Anchor, page: *Page) ![]const u8 { } pub fn setHref(self: *Anchor, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("href", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("href"), .wrap(value), page); } pub fn getTarget(self: *Anchor) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("target")) orelse ""; + return self.asElement().getAttributeSafe(comptime .wrap("target")) orelse ""; } pub fn setTarget(self: *Anchor, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("target", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("target"), .wrap(value), page); } pub fn getOrigin(self: *Anchor, page: *Page) ![]const u8 { @@ -167,19 +167,19 @@ pub fn setProtocol(self: *Anchor, value: []const u8, page: *Page) !void { } pub fn getType(self: *Anchor) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("type")) orelse ""; + return self.asElement().getAttributeSafe(comptime .wrap("type")) orelse ""; } pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("type", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page); } pub fn getName(self: *const Anchor) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Anchor, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), page); } pub fn getText(self: *Anchor, page: *Page) ![:0]const u8 { @@ -191,7 +191,7 @@ pub fn setText(self: *Anchor, value: []const u8, page: *Page) !void { } fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 { - const href = self.asElement().getAttributeSafe(comptime .literal("href")) orelse return null; + const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return null; if (href.len == 0) { return null; } diff --git a/src/browser/webapi/element/html/Audio.zig b/src/browser/webapi/element/html/Audio.zig index a29926aa..4f7e7491 100644 --- a/src/browser/webapi/element/html/Audio.zig +++ b/src/browser/webapi/element/html/Audio.zig @@ -16,6 +16,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +const String = @import("../../../../string.zig").String; + const js = @import("../../../js/js.zig"); const Page = @import("../../../Page.zig"); @@ -27,16 +29,16 @@ const Audio = @This(); _proto: *Media, -pub fn constructor(maybe_url: ?[]const u8, page: *Page) !*Media { +pub fn constructor(maybe_url: ?String, page: *Page) !*Media { const node = try page.createElementNS(.html, "audio", null); const el = node.as(Element); const list = try el.getOrCreateAttributeList(page); // Always set to "auto" initially. - _ = try list.putSafe("preload", "auto", el, page); + _ = try list.putSafe(comptime .wrap("preload"), comptime .wrap("auto"), el, page); // Set URL if provided. if (maybe_url) |url| { - _ = try list.putSafe("src", url, el, page); + _ = try list.putSafe(comptime .wrap("src"), url, el, page); } return node.as(Media); diff --git a/src/browser/webapi/element/html/Body.zig b/src/browser/webapi/element/html/Body.zig index f00b60d1..7b5b530e 100644 --- a/src/browser/webapi/element/html/Body.zig +++ b/src/browser/webapi/element/html/Body.zig @@ -49,7 +49,7 @@ pub const JsApi = struct { pub const Build = struct { pub fn complete(node: *Node, page: *Page) !void { const el = node.as(Element); - const on_load = el.getAttributeSafe(comptime .literal("onload")) orelse return; + const on_load = el.getAttributeSafe(comptime .wrap("onload")) orelse return; if (page.js.stringToPersistedFunction(on_load)) |func| { page.window._on_load = func; } else |err| { diff --git a/src/browser/webapi/element/html/Button.zig b/src/browser/webapi/element/html/Button.zig index 9c3d342e..89bb3575 100644 --- a/src/browser/webapi/element/html/Button.zig +++ b/src/browser/webapi/element/html/Button.zig @@ -39,50 +39,50 @@ pub fn asNode(self: *Button) *Node { } pub fn getDisabled(self: *const Button) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null; } pub fn setDisabled(self: *Button, disabled: bool, page: *Page) !void { if (disabled) { - try self.asElement().setAttributeSafe("disabled", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page); } else { - try self.asElement().removeAttribute("disabled", page); + try self.asElement().removeAttribute(comptime .wrap("disabled"), page); } } pub fn getName(self: *const Button) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Button, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } pub fn getType(self: *const Button) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("type")) orelse "submit"; + return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "submit"; } pub fn setType(self: *Button, typ: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("type", typ, page); + try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(typ), page); } pub fn getValue(self: *const Button) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("value")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("value")) orelse ""; } pub fn setValue(self: *Button, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("value", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(value), page); } pub fn getRequired(self: *const Button) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null; } pub fn setRequired(self: *Button, required: bool, page: *Page) !void { if (required) { - try self.asElement().setAttributeSafe("required", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page); } else { - try self.asElement().removeAttribute("required", page); + try self.asElement().removeAttribute(comptime .wrap("required"), page); } } @@ -90,7 +90,7 @@ pub fn getForm(self: *Button, page: *Page) ?*Form { const element = self.asElement(); // If form attribute exists, ONLY use that (even if it references nothing) - if (element.getAttributeSafe(comptime .literal("form"))) |form_id| { + if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { if (page.document.getElementById(form_id, page)) |form_element| { return form_element.is(Form); } diff --git a/src/browser/webapi/element/html/Canvas.zig b/src/browser/webapi/element/html/Canvas.zig index 1021eb35..122552b5 100644 --- a/src/browser/webapi/element/html/Canvas.zig +++ b/src/browser/webapi/element/html/Canvas.zig @@ -40,23 +40,23 @@ pub fn asNode(self: *Canvas) *Node { } pub fn getWidth(self: *const Canvas) u32 { - const attr = self.asConstElement().getAttributeSafe(comptime .literal("width")) orelse return 300; + const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 300; return std.fmt.parseUnsigned(u32, attr, 10) catch 300; } pub fn setWidth(self: *Canvas, value: u32, page: *Page) !void { const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); - try self.asElement().setAttributeSafe("width", str, page); + try self.asElement().setAttributeSafe(comptime .wrap("width"), .wrap(str), page); } pub fn getHeight(self: *const Canvas) u32 { - const attr = self.asConstElement().getAttributeSafe(comptime .literal("height")) orelse return 150; + const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 150; return std.fmt.parseUnsigned(u32, attr, 10) catch 150; } pub fn setHeight(self: *Canvas, value: u32, page: *Page) !void { const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); - try self.asElement().setAttributeSafe("height", str, page); + try self.asElement().setAttributeSafe(comptime .wrap("height"), .wrap(str), page); } /// Since there's no base class rendering contextes inherit from, diff --git a/src/browser/webapi/element/html/Custom.zig b/src/browser/webapi/element/html/Custom.zig index 9965be17..832c937b 100644 --- a/src/browser/webapi/element/html/Custom.zig +++ b/src/browser/webapi/element/html/Custom.zig @@ -64,7 +64,7 @@ pub fn invokeDisconnectedCallback(self: *Custom, page: *Page) void { self.invokeCallback("disconnectedCallback", .{}, page); } -pub fn invokeAttributeChangedCallback(self: *Custom, name: []const u8, old_value: ?[]const u8, new_value: ?[]const u8, page: *Page) void { +pub fn invokeAttributeChangedCallback(self: *Custom, name: String, old_value: ?String, new_value: ?String, page: *Page) void { const definition = self._definition orelse return; if (!definition.isAttributeObserved(name)) { return; @@ -144,7 +144,7 @@ pub fn invokeDisconnectedCallbackOnElement(element: *Element, page: *Page) void invokeCallbackOnElement(element, definition, "disconnectedCallback", .{}, page); } -pub fn invokeAttributeChangedCallbackOnElement(element: *Element, name: []const u8, old_value: ?[]const u8, new_value: ?[]const u8, page: *Page) void { +pub fn invokeAttributeChangedCallbackOnElement(element: *Element, name: String, old_value: ?String, new_value: ?String, page: *Page) void { // Autonomous custom element if (element.is(Custom)) |custom| { custom.invokeAttributeChangedCallback(name, old_value, new_value, page); @@ -174,7 +174,7 @@ fn invokeCallbackOnElement(element: *Element, definition: *CustomElementDefiniti // Check if element has "is" attribute and attach customized built-in definition pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void { - const is_value = element.getAttributeSafe(comptime .literal("is")) orelse return; + const is_value = element.getAttributeSafe(comptime .wrap("is")) orelse return; const custom_elements = page.window.getCustomElements(); const definition = custom_elements._definitions.get(is_value) orelse return; diff --git a/src/browser/webapi/element/html/Data.zig b/src/browser/webapi/element/html/Data.zig index 818042c2..421709d7 100644 --- a/src/browser/webapi/element/html/Data.zig +++ b/src/browser/webapi/element/html/Data.zig @@ -36,11 +36,11 @@ pub fn asNode(self: *Data) *Node { } pub fn getValue(self: *Data) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("value")) orelse ""; + return self.asElement().getAttributeSafe(comptime .wrap("value")) orelse ""; } pub fn setValue(self: *Data, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("value", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Dialog.zig b/src/browser/webapi/element/html/Dialog.zig index 89049961..65ab02ed 100644 --- a/src/browser/webapi/element/html/Dialog.zig +++ b/src/browser/webapi/element/html/Dialog.zig @@ -20,23 +20,23 @@ pub fn asNode(self: *Dialog) *Node { } pub fn getOpen(self: *const Dialog) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("open")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("open")) != null; } pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void { if (open) { - try self.asElement().setAttributeSafe("open", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("open"), .wrap(""), page); } else { - try self.asElement().removeAttribute("open", page); + try self.asElement().removeAttribute(comptime .wrap("open"), page); } } pub fn getReturnValue(self: *const Dialog) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("returnvalue")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("returnvalue")) orelse ""; } pub fn setReturnValue(self: *Dialog, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("returnvalue", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("returnvalue"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Form.zig b/src/browser/webapi/element/html/Form.zig index 71a98ea4..8a9279ac 100644 --- a/src/browser/webapi/element/html/Form.zig +++ b/src/browser/webapi/element/html/Form.zig @@ -43,15 +43,15 @@ pub fn asNode(self: *Form) *Node { } pub fn getName(self: *const Form) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Form, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } pub fn getMethod(self: *const Form) []const u8 { - const method = self.asConstElement().getAttributeSafe(comptime .literal("method")) orelse return "get"; + const method = self.asConstElement().getAttributeSafe(comptime .wrap("method")) orelse return "get"; if (std.ascii.eqlIgnoreCase(method, "post")) { return "post"; @@ -64,11 +64,11 @@ pub fn getMethod(self: *const Form) []const u8 { } pub fn setMethod(self: *Form, method: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("method", method, page); + try self.asElement().setAttributeSafe(comptime .wrap("method"), .wrap(method), page); } pub fn getElements(self: *Form, page: *Page) !*collections.HTMLFormControlsCollection { - const form_id = self.asElement().getAttributeSafe(comptime .literal("id")); + const form_id = self.asElement().getAttributeSafe(comptime .wrap("id")); const root = if (form_id != null) self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls else diff --git a/src/browser/webapi/element/html/Image.zig b/src/browser/webapi/element/html/Image.zig index c159fa97..41482ec9 100644 --- a/src/browser/webapi/element/html/Image.zig +++ b/src/browser/webapi/element/html/Image.zig @@ -15,11 +15,11 @@ pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image { if (w_) |w| blk: { const w_string = std.fmt.bufPrint(&page.buf, "{d}", .{w}) catch break :blk; - try el.setAttributeSafe("width", w_string, page); + try el.setAttributeSafe(comptime .wrap("width"), .wrap(w_string), page); } if (h_) |h| blk: { const h_string = std.fmt.bufPrint(&page.buf, "{d}", .{h}) catch break :blk; - try el.setAttributeSafe("height", h_string, page); + try el.setAttributeSafe(comptime .wrap("height"), .wrap(h_string), page); } return el.as(Image); } @@ -36,7 +36,7 @@ pub fn asNode(self: *Image) *Node { pub fn getSrc(self: *const Image, page: *Page) ![]const u8 { const element = self.asConstElement(); - const src = element.getAttributeSafe(comptime .literal("src")) orelse return ""; + const src = element.getAttributeSafe(comptime .wrap("src")) orelse return ""; if (src.len == 0) { return ""; } @@ -46,54 +46,54 @@ pub fn getSrc(self: *const Image, page: *Page) ![]const u8 { } pub fn setSrc(self: *Image, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("src", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), page); } pub fn getAlt(self: *const Image) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("alt")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("alt")) orelse ""; } pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("alt", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("alt"), .wrap(value), page); } pub fn getWidth(self: *const Image) u32 { - const attr = self.asConstElement().getAttributeSafe(comptime .literal("width")) orelse return 0; + const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 0; return std.fmt.parseUnsigned(u32, attr, 10) catch 0; } pub fn setWidth(self: *Image, value: u32, page: *Page) !void { const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); - try self.asElement().setAttributeSafe("width", str, page); + try self.asElement().setAttributeSafe(comptime .wrap("width"), .wrap(str), page); } pub fn getHeight(self: *const Image) u32 { - const attr = self.asConstElement().getAttributeSafe(comptime .literal("height")) orelse return 0; + const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 0; return std.fmt.parseUnsigned(u32, attr, 10) catch 0; } pub fn setHeight(self: *Image, value: u32, page: *Page) !void { const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); - try self.asElement().setAttributeSafe("height", str, page); + try self.asElement().setAttributeSafe(comptime .wrap("height"), .wrap(str), page); } pub fn getCrossOrigin(self: *const Image) ?[]const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("crossorigin")); + return self.asConstElement().getAttributeSafe(comptime .wrap("crossorigin")); } pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void { if (value) |v| { - return self.asElement().setAttributeSafe("crossorigin", v, page); + return self.asElement().setAttributeSafe(comptime .wrap("crossorigin"), .wrap(v), page); } - return self.asElement().removeAttribute("crossorigin", page); + return self.asElement().removeAttribute(comptime .wrap("crossorigin"), page); } pub fn getLoading(self: *const Image) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("loading")) orelse "eager"; + return self.asConstElement().getAttributeSafe(comptime .wrap("loading")) orelse "eager"; } pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("loading", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("loading"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Input.zig b/src/browser/webapi/element/html/Input.zig index 15d8c1f8..d6fe71c2 100644 --- a/src/browser/webapi/element/html/Input.zig +++ b/src/browser/webapi/element/html/Input.zig @@ -17,6 +17,8 @@ // along with this program. If not, see . const std = @import("std"); +const String = @import("../../../../string.zig").String; + const js = @import("../../../js/js.zig"); const Page = @import("../../../Page.zig"); @@ -98,7 +100,7 @@ pub fn getType(self: *const Input) []const u8 { pub fn setType(self: *Input, typ: []const u8, page: *Page) !void { // Setting the type property should update the attribute, which will trigger attributeChange const type_enum = Type.fromString(typ); - try self.asElement().setAttributeSafe("type", type_enum.toString(), page); + try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(type_enum.toString()), page); } pub fn getValue(self: *const Input) []const u8 { @@ -116,7 +118,7 @@ pub fn getDefaultValue(self: *const Input) []const u8 { } pub fn setDefaultValue(self: *Input, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("value", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(value), page); } pub fn getChecked(self: *const Input) bool { @@ -147,52 +149,52 @@ pub fn getDefaultChecked(self: *const Input) bool { pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void { if (checked) { - try self.asElement().setAttributeSafe("checked", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("checked"), .wrap(""), page); } else { - try self.asElement().removeAttribute("checked", page); + try self.asElement().removeAttribute(comptime .wrap("checked"), page); } } pub fn getDisabled(self: *const Input) bool { // TODO: Also check for disabled fieldset ancestors // (but not if we're inside a of that fieldset) - return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null; } pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void { if (disabled) { - try self.asElement().setAttributeSafe("disabled", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page); } else { - try self.asElement().removeAttribute("disabled", page); + try self.asElement().removeAttribute(comptime .wrap("disabled"), page); } } pub fn getName(self: *const Input) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Input, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } pub fn getAccept(self: *const Input) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("accept")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("accept")) orelse ""; } pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("accept", accept, page); + try self.asElement().setAttributeSafe(comptime .wrap("accept"), .wrap(accept), page); } pub fn getAlt(self: *const Input) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("alt")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("alt")) orelse ""; } pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("alt", alt, page); + try self.asElement().setAttributeSafe(comptime .wrap("alt"), .wrap(alt), page); } pub fn getMaxLength(self: *const Input) i32 { - const attr = self.asConstElement().getAttributeSafe(comptime .literal("maxlength")) orelse return -1; + const attr = self.asConstElement().getAttributeSafe(comptime .wrap("maxlength")) orelse return -1; return std.fmt.parseInt(i32, attr, 10) catch -1; } @@ -202,11 +204,11 @@ pub fn setMaxLength(self: *Input, max_length: i32, page: *Page) !void { } var buf: [32]u8 = undefined; const value = std.fmt.bufPrint(&buf, "{d}", .{max_length}) catch unreachable; - try self.asElement().setAttributeSafe("maxlength", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("maxlength"), .wrap(value), page); } pub fn getSize(self: *const Input) i32 { - const attr = self.asConstElement().getAttributeSafe(comptime .literal("size")) orelse return 20; + const attr = self.asConstElement().getAttributeSafe(comptime .wrap("size")) orelse return 20; const parsed = std.fmt.parseInt(i32, attr, 10) catch return 20; return if (parsed == 0) 20 else parsed; } @@ -216,46 +218,46 @@ pub fn setSize(self: *Input, size: i32, page: *Page) !void { return error.ZeroNotAllowed; } if (size < 0) { - return self.asElement().setAttributeSafe("size", "20", page); + return self.asElement().setAttributeSafe(comptime .wrap("size"), .wrap("20"), page); } var buf: [32]u8 = undefined; const value = std.fmt.bufPrint(&buf, "{d}", .{size}) catch unreachable; - try self.asElement().setAttributeSafe("size", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("size"), .wrap(value), page); } pub fn getSrc(self: *const Input, page: *Page) ![]const u8 { - const src = self.asConstElement().getAttributeSafe(comptime .literal("src")) orelse return ""; + const src = self.asConstElement().getAttributeSafe(comptime .wrap("src")) orelse return ""; // If attribute is explicitly set (even if empty), resolve it against the base URL return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{}); } pub fn setSrc(self: *Input, src: []const u8, page: *Page) !void { const trimmed = std.mem.trim(u8, src, &std.ascii.whitespace); - try self.asElement().setAttributeSafe("src", trimmed, page); + try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(trimmed), page); } pub fn getReadonly(self: *const Input) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("readonly")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("readonly")) != null; } pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void { if (readonly) { - try self.asElement().setAttributeSafe("readonly", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("readonly"), .wrap(""), page); } else { - try self.asElement().removeAttribute("readonly", page); + try self.asElement().removeAttribute(comptime .wrap("readonly"), page); } } pub fn getRequired(self: *const Input) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null; } pub fn setRequired(self: *Input, required: bool, page: *Page) !void { if (required) { - try self.asElement().setAttributeSafe("required", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page); } else { - try self.asElement().removeAttribute("required", page); + try self.asElement().removeAttribute(comptime .wrap("required"), page); } } @@ -379,7 +381,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form { const element = self.asElement(); // If form attribute exists, ONLY use that (even if it references nothing) - if (element.getAttributeSafe(comptime .literal("form"))) |form_id| { + if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { if (page.document.getElementById(form_id, page)) |form_element| { return form_element.is(Form); } @@ -402,7 +404,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form { fn uncheckRadioGroup(self: *Input, page: *Page) !void { const element = self.asElement(); - const name = element.getAttributeSafe(comptime .literal("name")) orelse return; + const name = element.getAttributeSafe(comptime .wrap("name")) orelse return; if (name.len == 0) { return; } @@ -420,7 +422,7 @@ fn uncheckRadioGroup(self: *Input, page: *Page) !void { continue; } - const other_name = other_element.getAttributeSafe(comptime .literal("name")) orelse continue; + const other_name = other_element.getAttributeSafe(comptime .wrap("name")) orelse continue; if (!std.mem.eql(u8, name, other_name)) { continue; } @@ -481,14 +483,14 @@ pub const Build = struct { const element = self.asElement(); // Store initial values from attributes - self._default_value = element.getAttributeSafe(comptime .literal("value")); - self._default_checked = element.getAttributeSafe(comptime .literal("checked")) != null; + self._default_value = element.getAttributeSafe(comptime .wrap("value")); + self._default_checked = element.getAttributeSafe(comptime .wrap("checked")) != null; // Current state starts equal to default self._value = self._default_value; self._checked = self._default_checked; - self._input_type = if (element.getAttributeSafe(comptime .literal("type"))) |type_attr| + self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr| Type.fromString(type_attr) else .text; @@ -499,12 +501,12 @@ pub const Build = struct { } } - pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, page: *Page) !void { - const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return; + pub fn attributeChange(element: *Element, name: String, value: String, page: *Page) !void { + const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return; const self = element.as(Input); switch (attribute) { - .type => self._input_type = Type.fromString(value), - .value => self._default_value = value, + .type => self._input_type = Type.fromString(value.str()), + .value => self._default_value = try page.arena.dupe(u8, value.str()), .checked => { self._default_checked = true; // Only update checked state if it hasn't been manually modified @@ -519,8 +521,8 @@ pub const Build = struct { } } - pub fn attributeRemove(element: *Element, name: []const u8, _: *Page) !void { - const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return; + pub fn attributeRemove(element: *Element, name: String, _: *Page) !void { + const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return; const self = element.as(Input); switch (attribute) { .type => self._input_type = .text, diff --git a/src/browser/webapi/element/html/Link.zig b/src/browser/webapi/element/html/Link.zig index 8595ce82..6fae0152 100644 --- a/src/browser/webapi/element/html/Link.zig +++ b/src/browser/webapi/element/html/Link.zig @@ -36,7 +36,7 @@ pub fn asNode(self: *Link) *Node { pub fn getHref(self: *Link, page: *Page) ![]const u8 { const element = self.asElement(); - const href = element.getAttributeSafe(comptime .literal("href")) orelse return ""; + const href = element.getAttributeSafe(comptime .wrap("href")) orelse return ""; if (href.len == 0) { return ""; } @@ -46,15 +46,15 @@ pub fn getHref(self: *Link, page: *Page) ![]const u8 { } pub fn setHref(self: *Link, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("href", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("href"), .wrap(value), page); } pub fn getRel(self: *Link) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("rel")) orelse return ""; + return self.asElement().getAttributeSafe(comptime .wrap("rel")) orelse return ""; } pub fn setRel(self: *Link, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("rel", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("rel"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Media.zig b/src/browser/webapi/element/html/Media.zig index 0d77506f..923836fa 100644 --- a/src/browser/webapi/element/html/Media.zig +++ b/src/browser/webapi/element/html/Media.zig @@ -219,7 +219,7 @@ pub fn setCurrentTime(self: *Media, value: f64) void { pub fn getSrc(self: *const Media, page: *Page) ![]const u8 { const element = self.asConstElement(); - const src = element.getAttributeSafe(comptime .literal("src")) orelse return ""; + const src = element.getAttributeSafe(comptime .wrap("src")) orelse return ""; if (src.len == 0) { return ""; } @@ -228,51 +228,51 @@ pub fn getSrc(self: *const Media, page: *Page) ![]const u8 { } pub fn setSrc(self: *Media, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("src", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("src"), .wrap(value), page); } pub fn getAutoplay(self: *const Media) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("autoplay")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("autoplay")) != null; } pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void { if (value) { - try self.asElement().setAttributeSafe("autoplay", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("autoplay"), .wrap(""), page); } else { - try self.asElement().removeAttribute("autoplay", page); + try self.asElement().removeAttribute(comptime .wrap("autoplay"), page); } } pub fn getControls(self: *const Media) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("controls")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("controls")) != null; } pub fn setControls(self: *Media, value: bool, page: *Page) !void { if (value) { - try self.asElement().setAttributeSafe("controls", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("controls"), .wrap(""), page); } else { - try self.asElement().removeAttribute("controls", page); + try self.asElement().removeAttribute(comptime .wrap("controls"), page); } } pub fn getLoop(self: *const Media) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("loop")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("loop")) != null; } pub fn setLoop(self: *Media, value: bool, page: *Page) !void { if (value) { - try self.asElement().setAttributeSafe("loop", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("loop"), .wrap(""), page); } else { - try self.asElement().removeAttribute("loop", page); + try self.asElement().removeAttribute(comptime .wrap("loop"), page); } } pub fn getPreload(self: *const Media) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("preload")) orelse "auto"; + return self.asConstElement().getAttributeSafe(comptime .wrap("preload")) orelse "auto"; } pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("preload", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("preload"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Meta.zig b/src/browser/webapi/element/html/Meta.zig index e4bb5eff..f8812d79 100644 --- a/src/browser/webapi/element/html/Meta.zig +++ b/src/browser/webapi/element/html/Meta.zig @@ -37,35 +37,35 @@ pub fn asNode(self: *Meta) *Node { } pub fn getName(self: *Meta) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("name")) orelse return ""; + return self.asElement().getAttributeSafe(comptime .wrap("name")) orelse return ""; } pub fn setName(self: *Meta, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), page); } pub fn getHttpEquiv(self: *Meta) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("http-equiv")) orelse return ""; + return self.asElement().getAttributeSafe(comptime .wrap("http-equiv")) orelse return ""; } pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("http-equiv", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("http-equiv"), .wrap(value), page); } pub fn getContent(self: *Meta) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("content")) orelse return ""; + return self.asElement().getAttributeSafe(comptime .wrap("content")) orelse return ""; } pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("content", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("content"), .wrap(value), page); } pub fn getMedia(self: *Meta) []const u8 { - return self.asElement().getAttributeSafe(comptime .literal("media")) orelse return ""; + return self.asElement().getAttributeSafe(comptime .wrap("media")) orelse return ""; } pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("media", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("media"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/element/html/Option.zig b/src/browser/webapi/element/html/Option.zig index bf4d3c8d..023b04e0 100644 --- a/src/browser/webapi/element/html/Option.zig +++ b/src/browser/webapi/element/html/Option.zig @@ -17,6 +17,8 @@ // along with this program. If not, see . const std = @import("std"); +const String = @import("../../../../string.zig").String; + const js = @import("../../../js/js.zig"); const Page = @import("../../../Page.zig"); @@ -55,7 +57,7 @@ pub fn getValue(self: *Option, page: *Page) []const u8 { pub fn setValue(self: *Option, value: []const u8, page: *Page) !void { const owned = try page.dupeString(value); - try self.asElement().setAttributeSafe("value", owned, page); + try self.asElement().setAttributeSafe(comptime .wrap("value"), .wrap(owned), page); self._value = owned; } @@ -87,18 +89,18 @@ pub fn getDisabled(self: *const Option) bool { pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void { self._disabled = disabled; if (disabled) { - try self.asElement().setAttributeSafe("disabled", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page); } else { - try self.asElement().removeAttribute("disabled", page); + try self.asElement().removeAttribute(comptime .wrap("disabled"), page); } } pub fn getName(self: *const Option) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Option, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } pub const JsApi = struct { @@ -124,21 +126,21 @@ pub const Build = struct { const element = self.asElement(); // Check for value attribute - self._value = element.getAttributeSafe(comptime .literal("value")); + self._value = element.getAttributeSafe(comptime .wrap("value")); // Check for selected attribute - self._default_selected = element.getAttributeSafe(comptime .literal("selected")) != null; + self._default_selected = element.getAttributeSafe(comptime .wrap("selected")) != null; self._selected = self._default_selected; // Check for disabled attribute - self._disabled = element.getAttributeSafe(comptime .literal("disabled")) != null; + self._disabled = element.getAttributeSafe(comptime .wrap("disabled")) != null; } - pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void { - const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return; + pub fn attributeChange(element: *Element, name: String, value: String, _: *Page) !void { + const attribute = std.meta.stringToEnum(enum { value, selected }, name.str()) orelse return; const self = element.as(Option); switch (attribute) { - .value => self._value = value, + .value => self._value = value.str(), .selected => { self._default_selected = true; self._selected = true; @@ -146,8 +148,8 @@ pub const Build = struct { } } - pub fn attributeRemove(element: *Element, name: []const u8, _: *Page) !void { - const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return; + pub fn attributeRemove(element: *Element, name: String, _: *Page) !void { + const attribute = std.meta.stringToEnum(enum { value, selected }, name.str()) orelse return; const self = element.as(Option); switch (attribute) { .value => self._value = null, diff --git a/src/browser/webapi/element/html/Script.zig b/src/browser/webapi/element/html/Script.zig index 60ff75e4..dae47ea1 100644 --- a/src/browser/webapi/element/html/Script.zig +++ b/src/browser/webapi/element/html/Script.zig @@ -53,27 +53,27 @@ pub fn getSrc(self: *const Script, page: *Page) ![]const u8 { pub fn setSrc(self: *Script, src: []const u8, page: *Page) !void { const element = self.asElement(); - try element.setAttributeSafe("src", src, page); - self._src = element.getAttributeSafe(comptime .literal("src")) orelse unreachable; + try element.setAttributeSafe(comptime .wrap("src"), .wrap(src), page); + self._src = element.getAttributeSafe(comptime .wrap("src")) orelse unreachable; if (element.asNode().isConnected()) { try page.scriptAddedCallback(false, self); } } pub fn getType(self: *const Script) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("type")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse ""; } pub fn setType(self: *Script, value: []const u8, page: *Page) !void { - return self.asElement().setAttributeSafe("type", value, page); + return self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page); } pub fn getNonce(self: *const Script) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("nonce")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("nonce")) orelse ""; } pub fn setNonce(self: *Script, value: []const u8, page: *Page) !void { - return self.asElement().setAttributeSafe("nonce", value, page); + return self.asElement().setAttributeSafe(comptime .wrap("nonce"), .wrap(value), page); } pub fn getOnLoad(self: *const Script) ?js.Function.Global { @@ -93,7 +93,7 @@ pub fn setOnError(self: *Script, cb: ?js.Function.Global) void { } pub fn getNoModule(self: *const Script) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("nomodule")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("nomodule")) != null; } pub fn setInnerText(self: *Script, text: []const u8, page: *Page) !void { @@ -127,9 +127,9 @@ pub const Build = struct { pub fn complete(node: *Node, page: *Page) !void { const self = node.as(Script); const element = self.asElement(); - self._src = element.getAttributeSafe(comptime .literal("src")) orelse ""; + self._src = element.getAttributeSafe(comptime .wrap("src")) orelse ""; - if (element.getAttributeSafe(comptime .literal("onload"))) |on_load| { + if (element.getAttributeSafe(comptime .wrap("onload"))) |on_load| { if (page.js.stringToPersistedFunction(on_load)) |func| { self._on_load = func; } else |err| { @@ -137,7 +137,7 @@ pub const Build = struct { } } - if (element.getAttributeSafe(comptime .literal("onerror"))) |on_error| { + if (element.getAttributeSafe(comptime .wrap("onerror"))) |on_error| { if (page.js.stringToPersistedFunction(on_error)) |func| { self._on_error = func; } else |err| { diff --git a/src/browser/webapi/element/html/Select.zig b/src/browser/webapi/element/html/Select.zig index fe84321c..2ad29b53 100644 --- a/src/browser/webapi/element/html/Select.zig +++ b/src/browser/webapi/element/html/Select.zig @@ -121,39 +121,39 @@ pub fn setSelectedIndex(self: *Select, index: i32) !void { } pub fn getMultiple(self: *const Select) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("multiple")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("multiple")) != null; } pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void { if (multiple) { - try self.asElement().setAttributeSafe("multiple", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("multiple"), .wrap(""), page); } else { - try self.asElement().removeAttribute("multiple", page); + try self.asElement().removeAttribute(comptime .wrap("multiple"), page); } } pub fn getDisabled(self: *const Select) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null; } pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void { if (disabled) { - try self.asElement().setAttributeSafe("disabled", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page); } else { - try self.asElement().removeAttribute("disabled", page); + try self.asElement().removeAttribute(comptime .wrap("disabled"), page); } } pub fn getName(self: *const Select) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Select, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } pub fn getSize(self: *const Select) u32 { - const s = self.asConstElement().getAttributeSafe(comptime .literal("size")) orelse return 0; + const s = self.asConstElement().getAttributeSafe(comptime .wrap("size")) orelse return 0; const trimmed = std.mem.trimLeft(u8, s, &std.ascii.whitespace); @@ -172,18 +172,18 @@ pub fn getSize(self: *const Select) u32 { pub fn setSize(self: *Select, size: u32, page: *Page) !void { const size_string = try std.fmt.allocPrint(page.call_arena, "{d}", .{size}); - try self.asElement().setAttributeSafe("size", size_string, page); + try self.asElement().setAttributeSafe(comptime .wrap("size"), .wrap(size_string), page); } pub fn getRequired(self: *const Select) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null; } pub fn setRequired(self: *Select, required: bool, page: *Page) !void { if (required) { - try self.asElement().setAttributeSafe("required", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page); } else { - try self.asElement().removeAttribute("required", page); + try self.asElement().removeAttribute(comptime .wrap("required"), page); } } @@ -218,7 +218,7 @@ pub fn getForm(self: *Select, page: *Page) ?*Form { const element = self.asElement(); // If form attribute exists, ONLY use that (even if it references nothing) - if (element.getAttributeSafe(comptime .literal("form"))) |form_id| { + if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { if (page.document.getElementById(form_id, page)) |form_element| { return form_element.is(Form); } diff --git a/src/browser/webapi/element/html/Slot.zig b/src/browser/webapi/element/html/Slot.zig index 37d10a7a..c7cd1aaa 100644 --- a/src/browser/webapi/element/html/Slot.zig +++ b/src/browser/webapi/element/html/Slot.zig @@ -25,11 +25,11 @@ pub fn asNode(self: *Slot) *Node { } pub fn getName(self: *const Slot) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *Slot, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } const AssignedNodesOptions = struct { @@ -131,7 +131,7 @@ fn isAssignedToSlot(node: *Node, slot_name: []const u8) bool { // Check if a node should be assigned to a slot with the given name if (node.is(Element)) |element| { // Get the slot attribute from the element - const node_slot = element.getAttributeSafe(comptime .literal("slot")) orelse ""; + const node_slot = element.getAttributeSafe(comptime .wrap("slot")) orelse ""; // Match if: // - Both are empty (default slot) diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index 66489bea..b9372bd3 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -39,38 +39,38 @@ pub fn asNode(self: *Style) *Node { // Attribute-backed properties pub fn getBlocking(self: *const Style) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("blocking")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("blocking")) orelse ""; } pub fn setBlocking(self: *Style, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("blocking", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("blocking"), .wrap(value), page); } pub fn getMedia(self: *const Style) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("media")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("media")) orelse ""; } pub fn setMedia(self: *Style, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("media", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("media"), .wrap(value), page); } pub fn getType(self: *const Style) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("type")) orelse "text/css"; + return self.asConstElement().getAttributeSafe(comptime .wrap("type")) orelse "text/css"; } pub fn setType(self: *Style, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("type", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("type"), .wrap(value), page); } pub fn getDisabled(self: *const Style) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null; } pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void { if (disabled) { - try self.asElement().setAttributeSafe("disabled", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page); } else { - try self.asElement().removeAttribute("disabled", page); + try self.asElement().removeAttribute(comptime .wrap("disabled"), page); } } diff --git a/src/browser/webapi/element/html/TextArea.zig b/src/browser/webapi/element/html/TextArea.zig index f0197901..31810832 100644 --- a/src/browser/webapi/element/html/TextArea.zig +++ b/src/browser/webapi/element/html/TextArea.zig @@ -84,34 +84,34 @@ pub fn setDefaultValue(self: *TextArea, value: []const u8, page: *Page) !void { } pub fn getDisabled(self: *const TextArea) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("disabled")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null; } pub fn setDisabled(self: *TextArea, disabled: bool, page: *Page) !void { if (disabled) { - try self.asElement().setAttributeSafe("disabled", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page); } else { - try self.asElement().removeAttribute("disabled", page); + try self.asElement().removeAttribute(comptime .wrap("disabled"), page); } } pub fn getName(self: *const TextArea) []const u8 { - return self.asConstElement().getAttributeSafe(comptime .literal("name")) orelse ""; + return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse ""; } pub fn setName(self: *TextArea, name: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("name", name, page); + try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(name), page); } pub fn getRequired(self: *const TextArea) bool { - return self.asConstElement().getAttributeSafe(comptime .literal("required")) != null; + return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null; } pub fn setRequired(self: *TextArea, required: bool, page: *Page) !void { if (required) { - try self.asElement().setAttributeSafe("required", "", page); + try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page); } else { - try self.asElement().removeAttribute("required", page); + try self.asElement().removeAttribute(comptime .wrap("required"), page); } } @@ -221,7 +221,7 @@ pub fn getForm(self: *TextArea, page: *Page) ?*Form { const element = self.asElement(); // If form attribute exists, ONLY use that (even if it references nothing) - if (element.getAttributeSafe(comptime .literal("form"))) |form_id| { + if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { if (page.document.getElementById(form_id, page)) |form_element| { return form_element.is(Form); } diff --git a/src/browser/webapi/element/html/Video.zig b/src/browser/webapi/element/html/Video.zig index 9774d1ca..57942e03 100644 --- a/src/browser/webapi/element/html/Video.zig +++ b/src/browser/webapi/element/html/Video.zig @@ -53,7 +53,7 @@ pub fn getVideoHeight(_: *const Video) u32 { pub fn getPoster(self: *const Video, page: *Page) ![]const u8 { const element = self.asConstElement(); - const poster = element.getAttributeSafe(comptime .literal("poster")) orelse return ""; + const poster = element.getAttributeSafe(comptime .wrap("poster")) orelse return ""; if (poster.len == 0) { return ""; } @@ -63,7 +63,7 @@ pub fn getPoster(self: *const Video, page: *Page) ![]const u8 { } pub fn setPoster(self: *Video, value: []const u8, page: *Page) !void { - try self.asElement().setAttributeSafe("poster", value, page); + try self.asElement().setAttributeSafe(comptime .wrap("poster"), .wrap(value), page); } pub const JsApi = struct { diff --git a/src/browser/webapi/net/FormData.zig b/src/browser/webapi/net/FormData.zig index 062e0af3..1e869411 100644 --- a/src/browser/webapi/net/FormData.zig +++ b/src/browser/webapi/net/FormData.zig @@ -127,7 +127,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa var elements = try form.getElements(page); var it = try elements.iterator(); while (it.next()) |element| { - if (element.getAttributeSafe(comptime .literal("disabled")) != null) { + if (element.getAttributeSafe(comptime .wrap("disabled")) != null) { continue; } @@ -139,7 +139,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa continue; } - const name = element.getAttributeSafe(comptime .literal("name")); + const name = element.getAttributeSafe(comptime .wrap("name")); 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 list.append(arena, x_key, "0"); @@ -148,7 +148,7 @@ fn collectForm(arena: Allocator, form_: ?*Form, submitter_: ?*Element, page: *Pa } } - const name = element.getAttributeSafe(comptime .literal("name")) orelse continue; + const name = element.getAttributeSafe(comptime .wrap("name")) orelse continue; const value = blk: { if (element.is(Form.Input)) |input| { const input_type = input._input_type; diff --git a/src/browser/webapi/net/XMLHttpRequest.zig b/src/browser/webapi/net/XMLHttpRequest.zig index 35773921..46e8bf7f 100644 --- a/src/browser/webapi/net/XMLHttpRequest.zig +++ b/src/browser/webapi/net/XMLHttpRequest.zig @@ -319,7 +319,11 @@ fn httpHeaderDoneCallback(transfer: *Http.Transfer) !bool { if (header.contentType()) |ct| { self._response_mime = Mime.parse(ct) catch |e| { - log.info(.http, "invalid content type", .{.content_Type = ct, .err = e, .url = self._url,}); + log.info(.http, "invalid content type", .{ + .content_Type = ct, + .err = e, + .url = self._url, + }); return false; }; } diff --git a/src/browser/webapi/selector/List.zig b/src/browser/webapi/selector/List.zig index 0f7d3c9c..ec2d90d3 100644 --- a/src/browser/webapi/selector/List.zig +++ b/src/browser/webapi/selector/List.zig @@ -412,11 +412,11 @@ fn matchesCompound(el: *Node.Element, compound: Selector.Compound, scope: *Node, fn matchesPart(el: *Node.Element, part: Part, scope: *Node, page: *Page) bool { switch (part) { .id => |id| { - const element_id = el.getAttributeSafe(comptime .literal("id")) orelse return false; + const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse return false; return std.mem.eql(u8, element_id, id); }, .class => |cls| { - const class_attr = el.getAttributeSafe(comptime .literal("class")) orelse return false; + const class_attr = el.getAttributeSafe(comptime .wrap("class")) orelse return false; return Selector.classAttributeContains(class_attr, cls); }, .tag => |tag| { @@ -526,10 +526,10 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N return input.getChecked(); }, .disabled => { - return el.getAttributeSafe(comptime .literal("disabled")) != null; + return el.getAttributeSafe(comptime .wrap("disabled")) != null; }, .enabled => { - return el.getAttributeSafe(comptime .literal("disabled")) == null; + return el.getAttributeSafe(comptime .wrap("disabled")) == null; }, .indeterminate => return false, @@ -537,19 +537,19 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N .valid => return false, .invalid => return false, .required => { - return el.getAttributeSafe(comptime .literal("required")) != null; + return el.getAttributeSafe(comptime .wrap("required")) != null; }, .optional => { - return el.getAttributeSafe(comptime .literal("required")) == null; + return el.getAttributeSafe(comptime .wrap("required")) == null; }, .in_range => return false, .out_of_range => return false, .placeholder_shown => return false, .read_only => { - return el.getAttributeSafe(comptime .literal("readonly")) != null; + return el.getAttributeSafe(comptime .wrap("readonly")) != null; }, .read_write => { - return el.getAttributeSafe(comptime .literal("readonly")) == null; + return el.getAttributeSafe(comptime .wrap("readonly")) == null; }, .default => return false, @@ -571,10 +571,10 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N .visited => return false, .any_link => { if (el.getTag() != .anchor) return false; - return el.getAttributeSafe(comptime .literal("href")) != null; + return el.getAttributeSafe(comptime .wrap("href")) != null; }, .target => { - const element_id = el.getAttributeSafe(comptime .literal("id")) orelse return false; + const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse return false; const location = page.document._location orelse return false; const hash = location.getHash(); if (hash.len <= 1) return false; diff --git a/src/browser/webapi/selector/Parser.zig b/src/browser/webapi/selector/Parser.zig index 08281240..c03f8483 100644 --- a/src/browser/webapi/selector/Parser.zig +++ b/src/browser/webapi/selector/Parser.zig @@ -846,9 +846,10 @@ fn attribute(self: *Parser, arena: Allocator, page: *Page) !Selector.Attribute { _ = self.skipSpaces(); const attr_name = try self.attributeName(); + // Normalize the name to lowercase for fast matching (consistent with Attribute.normalizeNameForLookup) - const normalized = try Attribute.normalizeNameForLookup(attr_name, page); - const name = try String.init(arena, normalized, .{}); + const normalized = try Attribute.normalizeNameForLookup(.wrap(attr_name), page); + const name = try normalized.dupe(arena); var case_insensitive = false; _ = self.skipSpaces(); diff --git a/src/cdp/AXNode.zig b/src/cdp/AXNode.zig index a96a5f57..272003a6 100644 --- a/src/cdp/AXNode.zig +++ b/src/cdp/AXNode.zig @@ -289,7 +289,7 @@ pub const Writer = struct { }, .input => { const input = el.as(DOMNode.Element.Html.Input); - const is_disabled = el.hasAttributeSafe(comptime .literal("disabled")); + const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled")); switch (input._input_type) { .text, .email, .tel, .url, .search, .password, .number => { @@ -305,8 +305,8 @@ pub const Writer = struct { try self.writeAXProperty(.{ .name = .settable, .value = .{ .booleanOrUndefined = true } }, w); } try self.writeAXProperty(.{ .name = .multiline, .value = .{ .boolean = false } }, w); - try self.writeAXProperty(.{ .name = .readonly, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("readonly")) } }, w); - try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("required")) } }, 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); }, .button, .submit, .reset, .image => { try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); @@ -319,14 +319,14 @@ pub const Writer = struct { if (!is_disabled) { try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w); } - const is_checked = el.hasAttributeSafe(comptime .literal("checked")); + const is_checked = el.hasAttributeSafe(comptime .wrap("checked")); try self.writeAXProperty(.{ .name = .checked, .value = .{ .token = if (is_checked) "true" else "false" } }, w); }, else => {}, } }, .textarea => { - const is_disabled = el.hasAttributeSafe(comptime .literal("disabled")); + const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled")); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); if (!is_disabled) { @@ -337,11 +337,11 @@ 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 .literal("readonly")) } }, w); - try self.writeAXProperty(.{ .name = .required, .value = .{ .boolean = el.hasAttributeSafe(comptime .literal("required")) } }, 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); }, .select => { - const is_disabled = el.hasAttributeSafe(comptime .literal("disabled")); + const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled")); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); if (!is_disabled) { @@ -385,7 +385,7 @@ pub const Writer = struct { } }, .button => { - const is_disabled = el.hasAttributeSafe(comptime .literal("disabled")); + const is_disabled = el.hasAttributeSafe(comptime .wrap("disabled")); try self.writeAXProperty(.{ .name = .invalid, .value = .{ .token = "false" } }, w); if (!is_disabled) { try self.writeAXProperty(.{ .name = .focusable, .value = .{ .booleanOrUndefined = true } }, w); @@ -629,10 +629,10 @@ pub const AXRole = enum(u8) { }, .textarea => .textbox, .select => { - if (el.getAttributeSafe(comptime .literal("multiple")) != null) { + if (el.getAttributeSafe(comptime .wrap("multiple")) != null) { return .listbox; } - if (el.getAttributeSafe(comptime .literal("size"))) |size| { + if (el.getAttributeSafe(comptime .wrap("size"))) |size| { if (!std.ascii.eqlIgnoreCase(size, "1")) { return .listbox; } @@ -649,7 +649,7 @@ pub const AXRole = enum(u8) { // Interactive Elements .anchor, .area => { - if (el.getAttributeSafe(comptime .literal("href")) == null) { + if (el.getAttributeSafe(comptime .wrap("href")) == null) { return .none; } @@ -669,7 +669,7 @@ pub const AXRole = enum(u8) { .thead, .tbody, .tfoot => .rowgroup, .tr => .row, .th => { - if (el.getAttributeSafe(comptime .literal("scope"))) |scope| { + if (el.getAttributeSafe(comptime .wrap("scope"))) |scope| { if (std.ascii.eqlIgnoreCase(scope, "row")) { return .rowheader; } @@ -722,7 +722,7 @@ pub fn fromNode(dom: *DOMNode) AXNode { break :blk null; } const elt = dom.as(DOMNode.Element); - break :blk elt.getAttributeSafe(comptime .literal("role")); + break :blk elt.getAttributeSafe(comptime .wrap("role")); }, }; } @@ -759,7 +759,7 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource { }, .element => |el| { // Handle aria-labelledby attribute (highest priority) - if (el.getAttributeSafe(.literal("aria-labelledby"))) |labelledby| { + if (el.getAttributeSafe(.wrap("aria-labelledby"))) |labelledby| { // Get the document to look up elements by ID const doc = node.ownerDocument(page) orelse return null; @@ -786,12 +786,12 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource { } } - if (el.getAttributeSafe(comptime .literal("aria-label"))) |aria_label| { + if (el.getAttributeSafe(comptime .wrap("aria-label"))) |aria_label| { try w.write(aria_label); return .aria_label; } - if (el.getAttributeSafe(comptime .literal("alt"))) |alt| { + if (el.getAttributeSafe(comptime .wrap("alt"))) |alt| { try w.write(alt); return .alt; } @@ -836,12 +836,12 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource { }, } - if (el.getAttributeSafe(comptime .literal("title"))) |title| { + if (el.getAttributeSafe(comptime .wrap("title"))) |title| { try w.write(title); return .title; } - if (el.getAttributeSafe(comptime .literal("placeholder"))) |placeholder| { + if (el.getAttributeSafe(comptime .wrap("placeholder"))) |placeholder| { try w.write(placeholder); return .placeholder; } @@ -857,17 +857,17 @@ fn writeName(axnode: AXNode, w: anytype, page: *Page) !?AXSource { } fn isHidden(elt: *DOMNode.Element) bool { - if (elt.getAttributeSafe(comptime .literal("aria-hidden"))) |value| { + if (elt.getAttributeSafe(comptime .wrap("aria-hidden"))) |value| { if (std.mem.eql(u8, value, "true")) { return true; } } - if (elt.hasAttributeSafe(comptime .literal("hidden"))) { + if (elt.hasAttributeSafe(comptime .wrap("hidden"))) { return true; } - if (elt.hasAttributeSafe(comptime .literal("inert"))) { + if (elt.hasAttributeSafe(comptime .wrap("inert"))) { return true; } @@ -940,7 +940,7 @@ fn isIgnore(self: AXNode, page: *Page) bool { // zig fmt: on .img => { // Check for empty decorative images - const alt_ = elt.getAttributeSafe(comptime .literal("alt")); + const alt_ = elt.getAttributeSafe(comptime .wrap("alt")); if (alt_ == null or alt_.?.len == 0) { return true; } @@ -967,9 +967,9 @@ fn isIgnore(self: AXNode, page: *Page) bool { // Generic containers with no semantic value if (tag == .div or tag == .span) { - const has_role = elt.hasAttributeSafe(comptime .literal("role")); - const has_aria_label = elt.hasAttributeSafe(comptime .literal("aria-label")); - const has_aria_labelledby = elt.hasAttributeSafe(.literal("aria-labelledby")); + const has_role = elt.hasAttributeSafe(comptime .wrap("role")); + const has_aria_label = elt.hasAttributeSafe(comptime .wrap("aria-label")); + const has_aria_labelledby = elt.hasAttributeSafe(.wrap("aria-labelledby")); if (!has_role and !has_aria_label and !has_aria_labelledby) { // Check if it has any non-ignored children diff --git a/src/string.zig b/src/string.zig index 234144f1..105d0841 100644 --- a/src/string.zig +++ b/src/string.zig @@ -20,6 +20,8 @@ const std = @import("std"); const js = @import("browser/js/js.zig"); const Allocator = std.mem.Allocator; +const M = @This(); + // German-string (small string optimization) pub const String = packed struct { len: i32, @@ -34,11 +36,15 @@ pub const String = packed struct { pub const empty = String{ .len = 0, .payload = .{ .content = @splat(0) } }; pub const deleted = String{ .len = tombstone, .payload = .{ .content = @splat(0) } }; - // Create a String from a string literal. For strings with len <= 12, the - // this can be done at comptime: comptime String.literal("id"); - // For strings with len > 12, this must be done at runtime. This is because, - // at comptime, we do not have a ptr for data and thus can't store it. - pub fn literal(input: anytype) String { + // for packages that already have String imported, then can use String.Global + pub const Global = M.Global; + + // Wraps an existing string. For strings with len <= 12, this can be done at + // comptime: comptime String.wrap("id"); + // For strings with len > 12, this must be done at runtime even for a string + // literal. This is because, at comptime, we do not have a ptr for data and + // thus can't store it. + pub fn wrap(input: anytype) String { if (@inComptime()) { const l = input.len; if (l > 12) { @@ -102,9 +108,8 @@ pub const String = packed struct { } } - pub fn fromJS(allocator: Allocator, js_obj: js.Object) !String { - const js_str = js_obj.toString(); - return init(allocator, js_str, .{}); + pub fn dupe(self: *const String, allocator: Allocator) !String { + return .init(allocator, self.str(), .{ .dupe = true }); } pub fn str(self: *const String) []const u8 {