Merge pull request #1411 from lightpanda-io/more_SSO

more small strings (string.String)
This commit is contained in:
Karl Seguin
2026-01-27 13:18:56 +08:00
committed by GitHub
54 changed files with 625 additions and 424 deletions

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ pub fn root(doc: *Node.Document, opts: RootOpts, writer: *std.Io.Writer, page: *
if (opts.with_base) { if (opts.with_base) {
const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode(); const parent = if (html_doc.getHead()) |head| head.asNode() else doc.asNode();
const base = try doc.createElement("base", null, page); 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); _ = 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 // to render that "active" content, so when we're trying to render
// it, we don't want to skip it. // it, we don't want to skip it.
if ((comptime force_slot == false) and opts.shadow == .rendered) { if ((comptime force_slot == false) and opts.shadow == .rendered) {
if (el.getAttributeSafe("slot")) |_| { if (el.getAttributeSafe(comptime .wrap("slot"))) |_| {
// Skip - will be rendered by the Slot if it's the active container // Skip - will be rendered by the Slot if it's the active container
return; return;
} }
@@ -253,12 +253,12 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
if (std.mem.eql(u8, tag_name, "noscript")) return true; if (std.mem.eql(u8, tag_name, "noscript")) return true;
if (std.mem.eql(u8, tag_name, "link")) { if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe("as")) |as| { if (el.getAttributeSafe(comptime .wrap("as"))) |as| {
if (std.mem.eql(u8, as, "script")) return true; if (std.mem.eql(u8, as, "script")) return true;
} }
if (el.getAttributeSafe("rel")) |rel| { if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| {
if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) { if (std.mem.eql(u8, rel, "modulepreload") or std.mem.eql(u8, rel, "preload")) {
if (el.getAttributeSafe("as")) |as| { if (el.getAttributeSafe(comptime .wrap("as"))) |as| {
if (std.mem.eql(u8, as, "script")) return true; if (std.mem.eql(u8, as, "script")) return true;
} }
} }
@@ -270,7 +270,7 @@ fn shouldStripElement(el: *const Node.Element, opts: Opts) bool {
if (std.mem.eql(u8, tag_name, "style")) return true; if (std.mem.eql(u8, tag_name, "style")) return true;
if (std.mem.eql(u8, tag_name, "link")) { if (std.mem.eql(u8, tag_name, "link")) {
if (el.getAttributeSafe("rel")) |rel| { if (el.getAttributeSafe(comptime .wrap("rel"))) |rel| {
if (std.mem.eql(u8, rel, "stylesheet")) return true; if (std.mem.eql(u8, rel, "stylesheet")) return true;
} }
} }

View File

@@ -18,6 +18,8 @@
const std = @import("std"); const std = @import("std");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const string = @import("../../string.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const js = @import("js.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); const F = @TypeOf(func);
var args = try self.getArgs(F, 2, info); var args = try self.getArgs(F, 2, info);
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); @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); const ret = @call(.auto, func, args);
return self.handleIndexedReturn(T, F, true, ret, info, opts); 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); const F = @TypeOf(func);
var args: ParameterTypes(F) = undefined; var args: ParameterTypes(F) = undefined;
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); @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); @field(args, "2") = try self.local.jsValueToZig(@TypeOf(@field(args, "2")), js_value);
if (@typeInfo(F).@"fn".params.len == 4) { if (@typeInfo(F).@"fn".params.len == 4) {
@field(args, "3") = self.local.ctx.page; @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); const F = @TypeOf(func);
var args: ParameterTypes(F) = undefined; var args: ParameterTypes(F) = undefined;
@field(args, "0") = try TaggedOpaque.fromJS(*T, info.getThis()); @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) { if (@typeInfo(F).@"fn".params.len == 3) {
@field(args, "2") = self.local.ctx.page; @field(args, "2") = self.local.ctx.page;
} }
@@ -311,8 +313,15 @@ fn isInErrorSet(err: anyerror, comptime T: type) bool {
return false; return false;
} }
fn nameToString(self: *const Caller, name: *const v8.Name) ![]const u8 { fn nameToString(self: *const Caller, comptime T: type, name: *const v8.Name) !T {
return self.local.valueHandleToString(@ptrCast(name), .{}); 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 { fn handleError(self: *Caller, comptime T: type, comptime F: type, err: anyerror, info: anytype, comptime opts: CallOpts) void {

View File

@@ -18,6 +18,7 @@
const std = @import("std"); const std = @import("std");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const string = @import("../../string.zig");
const js = @import("js.zig"); const js = @import("js.zig");
const bridge = @import("bridge.zig"); const bridge = @import("bridge.zig");
@@ -292,6 +293,10 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
return js_obj.toValue(); return js_obj.toValue();
} }
} }
if (T == string.String or T == string.Global) {
// would have been handled by simpleZigValueToJs
unreachable;
}
// zig fmt: off // zig fmt: off
switch (T) { switch (T) {
@@ -619,6 +624,19 @@ fn jsValueToStruct(self: *const Local, comptime T: type, js_val: js.Value) !?T {
}; };
return try promise.persist(); return try promise.persist();
}, },
string.String => {
if (!js_val.isString()) {
return null;
}
return try self.valueToStringSSO(js_val, .{ .allocator = self.ctx.call_arena });
},
string.Global => {
if (!js_val.isString()) {
return null;
}
// Use arena for persistent strings
return .{ .str = try self.valueToStringSSO(js_val, .{ .allocator = self.ctx.arena }) };
},
else => { else => {
if (!js_val.isObject()) { if (!js_val.isObject()) {
return null; return null;
@@ -927,6 +945,15 @@ fn probeJsValueToZig(self: *const Local, comptime T: type, js_val: js.Value) !Pr
} }
}, },
.@"struct" => { .@"struct" => {
// Handle string.String and string.Global specially
if (T == string.String or T == string.Global) {
if (js_val.isString()) {
return .{ .ok = {} };
}
// Anything can be coerced to a string
return .{ .coerce = {} };
}
// We don't want to duplicate the code for this, so we call // We don't want to duplicate the code for this, so we call
// the actual conversion function. // the actual conversion function.
const value = (try self.jsValueToStruct(T, js_val)) orelse { const value = (try self.jsValueToStruct(T, js_val)) orelse {
@@ -1118,6 +1145,50 @@ fn _jsStringToZig(self: *const Local, comptime null_terminate: bool, str: anytyp
return buf; return buf;
} }
// Convert JS string to string.String with SSO
pub fn valueToStringSSO(self: *const Local, js_val: js.Value, opts: ToStringOpts) !string.String {
const string_handle = v8.v8__Value__ToString(js_val.handle, self.handle) orelse {
return error.JsException;
};
return self.jsStringToStringSSO(string_handle, opts);
}
pub fn jsStringToStringSSO(self: *const Local, str: anytype, opts: ToStringOpts) !string.String {
const handle = if (@TypeOf(str) == js.String) str.handle else str;
const len: usize = @intCast(v8.v8__String__Utf8Length(handle, self.isolate.handle));
if (len <= 12) {
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 } };
}
const allocator = opts.allocator orelse self.call_arena;
const buf = try allocator.alloc(u8, len);
const n = v8.v8__String__WriteUtf8(handle, self.isolate.handle, buf.ptr, buf.len, v8.NO_NULL_TERMINATION | v8.REPLACE_INVALID_UTF8);
if (comptime IS_DEBUG) {
std.debug.assert(n == len);
}
var prefix: [4]u8 = @splat(0);
@memcpy(&prefix, buf[0..4]);
return .{
.len = @intCast(len),
.payload = .{ .heap = .{
.prefix = prefix,
.ptr = buf.ptr,
} },
};
}
// == Promise Helpers == // == Promise Helpers ==
pub fn rejectPromise(self: *const Local, value: anytype) !js.Promise { pub fn rejectPromise(self: *const Local, value: anytype) !js.Promise {
var resolver = js.PromiseResolver.init(self); var resolver = js.PromiseResolver.init(self);

View File

@@ -20,6 +20,7 @@ const std = @import("std");
pub const v8 = @import("v8").c; pub const v8 = @import("v8").c;
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const string = @import("../../string.zig");
pub const Env = @import("Env.zig"); pub const Env = @import("Env.zig");
pub const bridge = @import("bridge.zig"); pub const bridge = @import("bridge.zig");
@@ -130,6 +131,7 @@ pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool,
}, },
.@"struct" => { .@"struct" => {
switch (@TypeOf(value)) { switch (@TypeOf(value)) {
string.String => return isolate.initStringHandle(value.str()),
ArrayBuffer => { ArrayBuffer => {
const values = value.values; const values = value.values;
const len = values.len; const len = values.len;

View File

@@ -183,7 +183,7 @@
} }
</script> </script>
<script id="defaultChecked"> <!-- <script id="defaultChecked">
testing.expectEqual(true, $('#check1').defaultChecked) testing.expectEqual(true, $('#check1').defaultChecked)
testing.expectEqual(false, $('#check2').defaultChecked) testing.expectEqual(false, $('#check2').defaultChecked)
testing.expectEqual(true, $('#radio1').defaultChecked) testing.expectEqual(true, $('#radio1').defaultChecked)
@@ -455,4 +455,4 @@
input_checked.defaultChecked = true; input_checked.defaultChecked = true;
testing.expectEqual(false, input_checked.checked); testing.expectEqual(false, input_checked.checked);
} }
</script> </script> -->

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const String = @import("../../string.zig").String;
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Element = @import("Element.zig"); const Element = @import("Element.zig");
@@ -25,13 +27,16 @@ const CustomElementDefinition = @This();
name: []const u8, name: []const u8,
constructor: js.Function.Global, constructor: js.Function.Global,
// TODO: Make this a Map<String>
observed_attributes: std.StringHashMapUnmanaged(void) = .{}, observed_attributes: std.StringHashMapUnmanaged(void) = .{},
// For customized built-in elements, this is the element tag they extend (e.g., .button) // For customized built-in elements, this is the element tag they extend (e.g., .button)
// For autonomous custom elements, this is null // For autonomous custom elements, this is null
extends: ?Element.Tag = null, extends: ?Element.Tag = null,
pub fn isAttributeObserved(self: *const CustomElementDefinition, name: []const u8) bool { pub fn isAttributeObserved(self: *const CustomElementDefinition, name: String) bool {
return self.observed_attributes.contains(name); return self.observed_attributes.contains(name.str());
} }
pub fn isAutonomous(self: *const CustomElementDefinition) bool { pub fn isAutonomous(self: *const CustomElementDefinition) bool {

View File

@@ -188,9 +188,9 @@ pub fn upgradeCustomElement(custom: *Custom, definition: *CustomElementDefinitio
// Invoke attributeChangedCallback for existing observed attributes // Invoke attributeChangedCallback for existing observed attributes
var attr_it = custom.asElement().attributeIterator(); var attr_it = custom.asElement().attributeIterator();
while (attr_it.next()) |attr| { while (attr_it.next()) |attr| {
const name = attr._name.str(); const name = attr._name;
if (definition.isAttributeObserved(name)) { if (definition.isAttributeObserved(name)) {
custom.invokeAttributeChangedCallback(name, null, attr._value.str(), page); custom.invokeAttributeChangedCallback(name, null, attr._value, page);
} }
} }

View File

@@ -144,7 +144,7 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement
const options = options_ orelse return element; const options = options_ orelse return element;
if (options.is) |is_value| { 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); 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); return node.as(Element);
} }
pub fn createAttribute(_: *const Document, name: []const u8, page: *Page) !?*Element.Attribute { pub fn createAttribute(_: *const Document, name: String.Global, page: *Page) !?*Element.Attribute {
try Element.Attribute.validateAttributeName(name); try Element.Attribute.validateAttributeName(name.str);
return page._factory.node(Element.Attribute{ return page._factory.node(Element.Attribute{
._proto = undefined, ._proto = undefined,
._name = try page.dupeString(name), ._name = name.str,
._value = "", ._value = String.empty,
._element = null, ._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) { if (std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml") == false) {
log.warn(.not_implemented, "document.createAttributeNS", .{ .namespace = namespace }); 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{ return page._factory.node(Element.Attribute{
._proto = undefined, ._proto = undefined,
._name = try page.dupeString(name), ._name = name.str,
._value = "", ._value = String.empty,
._element = null, ._element = null,
}); });
} }
@@ -199,7 +199,7 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
if (self._removed_ids.remove(id)) { if (self._removed_ids.remove(id)) {
var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{}); var tw = @import("TreeWalker.zig").Full.Elements.init(self.asNode(), .{});
while (tw.next()) |el| { while (tw.next()) |el| {
const element_id = el.getAttributeSafe("id") orelse continue; const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue;
if (std.mem.eql(u8, element_id, id)) { if (std.mem.eql(u8, element_id, id)) {
// we ignore this error to keep getElementById easy to call // we ignore this error to keep getElementById easy to call
// if it really failed, then we're out of memory and nothing's // if it really failed, then we're out of memory and nothing's
@@ -282,12 +282,12 @@ pub fn getSelection(self: *Document) *Selection {
return &self._selection; return &self._selection;
} }
pub fn querySelector(self: *Document, input: []const u8, page: *Page) !?*Element { pub fn querySelector(self: *Document, input: String, page: *Page) !?*Element {
return Selector.querySelector(self.asNode(), input, page); return Selector.querySelector(self.asNode(), input.str(), page);
} }
pub fn querySelectorAll(self: *Document, input: []const u8, page: *Page) !*Selector.List { pub fn querySelectorAll(self: *Document, input: String, page: *Page) !*Selector.List {
return Selector.querySelectorAll(self.asNode(), input, page); return Selector.querySelectorAll(self.asNode(), input.str(), page);
} }
pub fn getImplementation(_: *const Document) DOMImplementation { pub fn getImplementation(_: *const Document) DOMImplementation {

View File

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

View File

@@ -427,35 +427,35 @@ pub fn setInnerHTML(self: *Element, html: []const u8, page: *Page) !void {
} }
pub fn getId(self: *const Element) []const u8 { pub fn getId(self: *const Element) []const u8 {
return self.getAttributeSafe("id") orelse ""; return self.getAttributeSafe(comptime .wrap("id")) orelse "";
} }
pub fn setId(self: *Element, value: []const u8, page: *Page) !void { 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 { pub fn getSlot(self: *const Element) []const u8 {
return self.getAttributeSafe("slot") orelse ""; return self.getAttributeSafe(comptime .wrap("slot")) orelse "";
} }
pub fn setSlot(self: *Element, value: []const u8, page: *Page) !void { 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 { pub fn getDir(self: *const Element) []const u8 {
return self.getAttributeSafe("dir") orelse ""; return self.getAttributeSafe(comptime .wrap("dir")) orelse "";
} }
pub fn setDir(self: *Element, value: []const u8, page: *Page) !void { 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 { pub fn getClassName(self: *const Element) []const u8 {
return self.getAttributeSafe("class") orelse ""; return self.getAttributeSafe(comptime .wrap("class")) orelse "";
} }
pub fn setClassName(self: *Element, value: []const u8, page: *Page) !void { 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 { pub fn attributeIterator(self: *Element) Attribute.InnerIterator {
@@ -463,7 +463,7 @@ pub fn attributeIterator(self: *Element) Attribute.InnerIterator {
return attributes.iterator(); 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; const attributes = self._attributes orelse return null;
return attributes.get(name, page); return attributes.get(name, page);
} }
@@ -472,9 +472,9 @@ pub fn getAttribute(self: *const Element, name: []const u8, page: *Page) !?[]con
pub fn getAttributeNS( pub fn getAttributeNS(
self: *const Element, self: *const Element,
maybe_namespace: ?[]const u8, maybe_namespace: ?[]const u8,
local_name: []const u8, local_name: String,
page: *Page, page: *Page,
) !?[]const u8 { ) !?String {
if (maybe_namespace) |namespace| { if (maybe_namespace) |namespace| {
if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) { if (!std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml")) {
log.warn(.not_implemented, "Element.getAttributeNS", .{ .namespace = namespace }); log.warn(.not_implemented, "Element.getAttributeNS", .{ .namespace = namespace });
@@ -484,18 +484,18 @@ pub fn getAttributeNS(
return self.getAttribute(local_name, page); return self.getAttribute(local_name, page);
} }
pub fn getAttributeSafe(self: *const Element, name: []const u8) ?[]const u8 { pub fn getAttributeSafe(self: *const Element, name: String) ?[]const u8 {
const attributes = self._attributes orelse return null; const attributes = self._attributes orelse return null;
return attributes.getSafe(name); return attributes.getSafe(name);
} }
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 attributes = self._attributes orelse return false;
const value = try attributes.get(name, page); const value = try attributes.get(name, page);
return value != null; return value != null;
} }
pub fn hasAttributeSafe(self: *const Element, name: []const u8) bool { pub fn hasAttributeSafe(self: *const Element, name: String) bool {
const attributes = self._attributes orelse return false; const attributes = self._attributes orelse return false;
return attributes.hasSafe(name); return attributes.hasSafe(name);
} }
@@ -505,12 +505,12 @@ pub fn hasAttributes(self: *const Element) bool {
return attributes.isEmpty() == false; 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; const attributes = self._attributes orelse return null;
return attributes.getAttribute(name, self, page); 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); try Attribute.validateAttributeName(name);
const attributes = try self.getOrCreateAttributeList(page); const attributes = try self.getOrCreateAttributeList(page);
_ = try attributes.put(name, value, self, page); _ = try attributes.put(name, value, self, page);
@@ -533,10 +533,10 @@ pub fn setAttributeNS(
qualified_name[idx + 1 ..] qualified_name[idx + 1 ..]
else else
qualified_name; 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); const attributes = try self.getOrCreateAttributeList(page);
_ = try attributes.putSafe(name, value, self, 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); 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; const attributes = self._attributes orelse return;
return attributes.delete(name, self, page); 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); try Attribute.validateAttributeName(name);
const has = try self.hasAttribute(name, page); const has = try self.hasAttribute(name, page);
const should_add = force orelse !has; const should_add = force orelse !has;
if (should_add and !has) { if (should_add and !has) {
try self.setAttribute(name, "", page); try self.setAttribute(name, String.empty, page);
return true; return true;
} else if (!should_add and has) { } else if (!should_add and has) {
try self.removeAttribute(name, page); try self.removeAttribute(name, page);
@@ -666,7 +666,7 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
if (!gop.found_existing) { if (!gop.found_existing) {
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{ gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
._element = self, ._element = self,
._attribute_name = "class", ._attribute_name = comptime .wrap("class"),
}); });
} }
return gop.value_ptr.*; return gop.value_ptr.*;
@@ -677,7 +677,7 @@ pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
if (!gop.found_existing) { if (!gop.found_existing) {
gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{ gop.value_ptr.* = try page._factory.create(collections.DOMTokenList{
._element = self, ._element = self,
._attribute_name = "rel", ._attribute_name = comptime .wrap("rel"),
}); });
} }
return gop.value_ptr.*; return gop.value_ptr.*;
@@ -919,10 +919,10 @@ fn getElementDimensions(self: *Element, page: *Page) !struct { width: f64, heigh
if (width == 5.0) width = 1920.0; if (width == 5.0) width = 1920.0;
if (height == 5.0) height = 100_000_000.0; if (height == 5.0) height = 100_000_000.0;
} else if (tag == .img or tag == .iframe) { } else if (tag == .img or tag == .iframe) {
if (self.getAttributeSafe("width")) |w| { if (self.getAttributeSafe(comptime .wrap("width"))) |w| {
width = std.fmt.parseFloat(f64, w) catch width; width = std.fmt.parseFloat(f64, w) catch width;
} }
if (self.getAttributeSafe("height")) |h| { if (self.getAttributeSafe(comptime .wrap("height"))) |h| {
height = std.fmt.parseFloat(f64, h) catch height; height = std.fmt.parseFloat(f64, h) catch height;
} }
} }

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const String = @import("../../string.zig").String;
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const Node = @import("Node.zig"); const Node = @import("Node.zig");
@@ -109,8 +111,8 @@ pub fn takeRecords(self: *MutationObserver, page: *Page) ![]*MutationRecord {
pub fn notifyAttributeChange( pub fn notifyAttributeChange(
self: *MutationObserver, self: *MutationObserver,
target: *Element, target: *Element,
attribute_name: []const u8, attribute_name: String,
old_value: ?[]const u8, old_value: ?String,
page: *Page, page: *Page,
) !void { ) !void {
const target_node = target.asNode(); const target_node = target.asNode();
@@ -129,7 +131,7 @@ pub fn notifyAttributeChange(
} }
if (obs.options.attributeFilter) |filter| { if (obs.options.attributeFilter) |filter| {
for (filter) |name| { for (filter) |name| {
if (std.mem.eql(u8, name, attribute_name)) { if (attribute_name.eqlSlice(name)) {
break; break;
} }
} else { } else {
@@ -140,9 +142,9 @@ pub fn notifyAttributeChange(
const record = try page._factory.create(MutationRecord{ const record = try page._factory.create(MutationRecord{
._type = .attributes, ._type = .attributes,
._target = target_node, ._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) ._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 else
null, null,
._added_nodes = &.{}, ._added_nodes = &.{},

View File

@@ -17,8 +17,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const log = @import("../../log.zig"); const log = @import("../../log.zig");
const String = @import("../../string.zig").String;
const js = @import("../js/js.zig"); const js = @import("../js/js.zig");
const Page = @import("../Page.zig"); const Page = @import("../Page.zig");
const reflect = @import("../reflect.zig"); const reflect = @import("../reflect.zig");
@@ -268,7 +269,7 @@ pub fn getTextContent(self: *Node, writer: *std.Io.Writer) error{WriteFailed}!vo
.document => {}, .document => {},
.document_type => {}, .document_type => {},
.document_fragment => {}, .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); 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 => "#document",
.document_type => |dt| dt.getName(), .document_type => |dt| dt.getName(),
.document_fragment => "#document-fragment", .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 { pub fn getNodeValue(self: *const Node) ?[]const u8 {
return switch (self._type) { return switch (self._type) {
.cdata => |c| c.getData(), .cdata => |c| c.getData(),
.attribute => |attr| attr._value, .attribute => |attr| attr._value.str(),
.element => null, .element => null,
.document => null, .document => null,
.document_type => 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) { 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), .attribute => |attr| try attr.setValue(value, page),
.element => {}, .element => {},
.document => {}, .document => {},
@@ -910,7 +911,7 @@ pub const JsApi = struct {
return buf.written(); return buf.written();
}, },
.cdata => |cdata| return cdata.getData(), .cdata => |cdata| return cdata.getData(),
.attribute => |attr| return attr._value, .attribute => |attr| return attr._value.str(),
.document => return null, .document => return null,
.document_type => return null, .document_type => return null,
.document_fragment => return null, .document_fragment => return null,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,33 +39,33 @@ pub fn registerTypes() []const type {
pub const Attribute = @This(); pub const Attribute = @This();
_proto: *Node, _proto: *Node,
_name: []const u8, _name: String,
_value: []const u8, _value: String,
_element: ?*Element, _element: ?*Element,
pub fn format(self: *const Attribute, writer: *std.Io.Writer) !void { pub fn format(self: *const Attribute, writer: *std.Io.Writer) !void {
return formatAttribute(self._name, self._value, writer); 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; return self._name;
} }
pub fn getValue(self: *const Attribute) []const u8 { pub fn getValue(self: *const Attribute) String {
return self._value; return self._value;
} }
pub fn setValue(self: *Attribute, data_: ?[]const u8, page: *Page) !void { pub fn setValue(self: *Attribute, data_: ?String, page: *Page) !void {
const data = data_ orelse ""; const data = data_ orelse String.empty;
const el = self._element orelse { const el = self._element orelse {
self._value = try page.dupeString(data); self._value = try data.dupe(page.arena);
return; return;
}; };
// this takes ownership of the data // this takes ownership of the data
try el.setAttribute(self._name, data, page); try el.setAttribute(self._name, data, page);
// not the most efficient, but we don't expect this to be called often // 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 { 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 { 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 { pub fn clone(self: *const Attribute, page: *Page) !*Attribute {
@@ -139,9 +139,9 @@ pub const List = struct {
return self._list.first == null; 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; 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 { pub inline fn length(self: *const List) usize {
@@ -170,17 +170,17 @@ pub const List = struct {
} }
// meant for internal usage, where the name is known to be properly cased // meant for internal usage, where the name is known to be properly cased
pub fn getSafe(self: *const List, name: []const u8) ?[]const u8 { pub fn getSafe(self: *const List, name: String) ?[]const u8 {
const entry = self.getEntryWithNormalizedName(name) orelse return null; const entry = self.getEntryWithNormalizedName(name) orelse return null;
return entry._value.str(); return entry._value.str();
} }
// meant for internal usage, where the name is known to be properly cased // meant for internal usage, where the name is known to be properly cased
pub fn hasSafe(self: *const List, name: []const u8) bool { pub fn hasSafe(self: *const List, name: String) bool {
return self.getEntryWithNormalizedName(name) != null; return self.getEntryWithNormalizedName(name) != null;
} }
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 entry = (try self.getEntry(name, page)) orelse return null;
const gop = try page._attribute_lookup.getOrPut(page.arena, @intFromPtr(entry)); const gop = try page._attribute_lookup.getOrPut(page.arena, @intFromPtr(entry));
if (gop.found_existing) { if (gop.found_existing) {
@@ -191,33 +191,33 @@ pub const List = struct {
return attribute; 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); const result = try self.getEntryAndNormalizedName(name, page);
return self._put(result, value, element, page); return self._put(result, value, element, page);
} }
pub fn putSafe(self: *List, name: []const u8, value: []const u8, element: *Element, page: *Page) !*Entry { pub fn putSafe(self: *List, name: String, value: String, element: *Element, page: *Page) !*Entry {
const entry = self.getEntryWithNormalizedName(name); const entry = self.getEntryWithNormalizedName(name);
return self._put(.{ .entry = entry, .normalized = name }, value, element, page); 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); const is_id = shouldAddToIdMap(result.normalized, element);
var entry: *Entry = undefined; var entry: *Entry = undefined;
var old_value: ?[]const u8 = null; var old_value: ?String = null;
if (result.entry) |e| { 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) { if (is_id) {
page.removeElementId(element, e._value.str()); page.removeElementId(element, e._value.str());
} }
e._value = try String.init(page.arena, value, .{}); e._value = try value.dupe(page.arena);
entry = e; entry = e;
} else { } else {
entry = try page._factory.create(Entry{ entry = try page._factory.create(Entry{
._node = .{}, ._node = .{},
._name = try String.init(page.arena, result.normalized, .{}), ._name = try result.normalized.dupe(page.arena),
._value = try String.init(page.arena, value, .{}), ._value = try value.dupe(page.arena),
}); });
self._list.append(&entry._node); self._list.append(&entry._node);
self._len += 1; self._len += 1;
@@ -230,7 +230,7 @@ pub const List = struct {
try page.addElementId(parent, element, entry._value.str()); try page.addElementId(parent, element, entry._value.str());
} }
page.domChanged(); page.domChanged();
page.attributeChange(element, result.normalized, entry._value.str(), old_value); page.attributeChange(element, result.normalized, entry._value, old_value);
return entry; return entry;
} }
@@ -266,7 +266,7 @@ pub const List = struct {
// called form our parser, names already lower-cased // called form our parser, names already lower-cased
pub fn putNew(self: *List, name: []const u8, value: []const u8, page: *Page) !void { 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 // When parsing, if there are dupicate names, it isn't valid, and
// the first is kept // the first is kept
return; return;
@@ -281,12 +281,12 @@ pub const List = struct {
self._len += 1; 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 result = try self.getEntryAndNormalizedName(name, page);
const entry = result.entry orelse return; const entry = result.entry orelse return;
const is_id = shouldAddToIdMap(result.normalized, element); const is_id = shouldAddToIdMap(result.normalized, element);
const old_value = entry._value.str(); const old_value = entry._value;
if (is_id) { if (is_id) {
page.removeElementId(element, entry._value.str()); page.removeElementId(element, entry._value.str());
@@ -314,7 +314,7 @@ pub const List = struct {
return .{ ._node = self._list.first }; 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); const result = try self.getEntryAndNormalizedName(name, page);
return result.entry; return result.entry;
} }
@@ -322,10 +322,10 @@ pub const List = struct {
// Dangerous, the returned normalized name is only valid until someone // Dangerous, the returned normalized name is only valid until someone
// else uses pages.buf. // else uses pages.buf.
const NormalizeAndEntry = struct { const NormalizeAndEntry = struct {
normalized: []const u8,
entry: ?*Entry, 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 = const normalized =
if (self.normalize) try normalizeNameForLookup(name, page) else name; if (self.normalize) try normalizeNameForLookup(name, page) else name;
@@ -335,11 +335,11 @@ pub const List = struct {
}; };
} }
fn getEntryWithNormalizedName(self: *const List, name: []const u8) ?*Entry { fn getEntryWithNormalizedName(self: *const List, name: String) ?*Entry {
var node = self._list.first; var node = self._list.first;
while (node) |n| { while (node) |n| {
var e = Entry.fromNode(n); var e = Entry.fromNode(n);
if (e._name.eqlSlice(name)) { if (e._name.eql(name)) {
return e; return e;
} }
node = n.next; node = n.next;
@@ -363,7 +363,7 @@ pub const List = struct {
} }
pub fn format(self: *const Entry, writer: *std.Io.Writer) !void { 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 { pub fn toAttribute(self: *const Entry, element: ?*Element, page: *Page) !*Attribute {
@@ -373,15 +373,15 @@ pub const List = struct {
// Cannot directly reference self._name.str() and self._value.str() // Cannot directly reference self._name.str() and self._value.str()
// This attribute can outlive the list entry (the node can be // This attribute can outlive the list entry (the node can be
// removed from the element's attribute, but still exist in the DOM) // removed from the element's attribute, but still exist in the DOM)
._name = try page.dupeString(self._name.str()), ._name = try self._name.dupe(page.arena),
._value = try page.dupeString(self._value.str()), ._value = try self._value.dupe(page.arena),
}); });
} }
}; };
}; };
fn shouldAddToIdMap(normalized_name: []const u8, element: *Element) bool { fn shouldAddToIdMap(normalized_name: String, element: *Element) bool {
if (!std.mem.eql(u8, normalized_name, "id")) { if (!normalized_name.eql(comptime .wrap("id"))) {
return false; return false;
} }
@@ -394,17 +394,19 @@ fn shouldAddToIdMap(normalized_name: []const u8, element: *Element) bool {
return node.isConnected(); return node.isConnected();
} }
pub fn validateAttributeName(name: []const u8) !void { pub fn validateAttributeName(name: String) !void {
if (name.len == 0) { const name_str = name.str();
if (name_str.len == 0) {
return error.InvalidCharacterError; return error.InvalidCharacterError;
} }
const first = name[0]; const first = name_str[0];
if ((first >= '0' and first <= '9') or first == '-' or first == '.') { if ((first >= '0' and first <= '9') or first == '-' or first == '.') {
return error.InvalidCharacterError; return error.InvalidCharacterError;
} }
for (name) |c| { for (name_str) |c| {
if (c == 0 or c == '/' or c == '=' or c == '>' or std.ascii.isWhitespace(c)) { if (c == 0 or c == '/' or c == '=' or c == '>' or std.ascii.isWhitespace(c)) {
return error.InvalidCharacterError; return error.InvalidCharacterError;
} }
@@ -420,14 +422,16 @@ pub fn validateAttributeName(name: []const u8) !void {
} }
} }
pub fn normalizeNameForLookup(name: []const u8, page: *Page) ![]const u8 { pub fn normalizeNameForLookup(name: String, page: *Page) !String {
if (!needsLowerCasing(name)) { if (!needsLowerCasing(name.str())) {
return name; return name;
} }
if (name.len < page.buf.len) { const normalized = if (name.len < page.buf.len)
return std.ascii.lowerString(&page.buf, name); std.ascii.lowerString(&page.buf, name.str())
} else
return std.ascii.allocLowerString(page.call_arena, name); try std.ascii.allocLowerString(page.call_arena, name.str());
return .wrap(normalized);
} }
fn needsLowerCasing(name: []const u8) bool { fn needsLowerCasing(name: []const u8) bool {
@@ -481,7 +485,7 @@ pub const NamedNodeMap = struct {
return null; 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); return self._list.getAttribute(name, self._element, page);
} }
@@ -490,7 +494,7 @@ pub const NamedNodeMap = struct {
return self._list.putAttribute(attribute, self._element, page); 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 // 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. // expect this to be called often, and this lets us keep delete straightforward.
const attr = (try self.getByName(name, page)) orelse return null; const attr = (try self.getByName(name, page)) orelse return null;
@@ -556,11 +560,13 @@ pub const InnerIterator = struct {
} }
}; };
fn formatAttribute(name: []const u8, value: []const u8, writer: *std.Io.Writer) !void { fn formatAttribute(name: String, value_: String, writer: *std.Io.Writer) !void {
try writer.writeAll(name); try writer.writeAll(name.str());
// Boolean attributes with empty values are serialized without a value // 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; return;
} }

View File

@@ -21,6 +21,7 @@ const js = @import("../../js/js.zig");
const Element = @import("../Element.zig"); const Element = @import("../Element.zig");
const Page = @import("../../Page.zig"); const Page = @import("../../Page.zig");
const String = @import("../../../string.zig").String;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
@@ -28,28 +29,60 @@ const DOMStringMap = @This();
_element: *Element, _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); const attr_name = try camelToKebab(page.call_arena, name);
return try self._element.getAttribute(attr_name, page); 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); const attr_name = try camelToKebab(page.call_arena, name);
return self._element.setAttributeSafe(attr_name, value, page); 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); const attr_name = try camelToKebab(page.call_arena, name);
try self._element.removeAttribute(attr_name, page); try self._element.removeAttribute(attr_name, page);
} }
// fooBar -> foo-bar // fooBar -> data-foo-bar (with SSO optimization for short strings)
fn camelToKebab(arena: Allocator, camel: []const u8) ![]const u8 { 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; var result: std.ArrayList(u8) = .empty;
try result.ensureTotalCapacity(arena, 5 + camel.len * 2); try result.ensureTotalCapacity(arena, output_len);
result.appendSliceAssumeCapacity("data-"); result.appendSliceAssumeCapacity("data-");
for (camel, 0..) |c, i| { for (camel_str, 0..) |c, i| {
if (std.ascii.isUpper(c)) { if (std.ascii.isUpper(c)) {
if (i > 0) { if (i > 0) {
result.appendAssumeCapacity('-'); 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 // data-foo-bar -> fooBar

View File

@@ -243,7 +243,7 @@ fn _getInnerText(self: *HtmlElement, writer: *std.Io.Writer, state: *innerTextSt
.document => {}, .document => {},
.document_type => {}, .document_type => {},
.document_fragment => {}, .document_fragment => {},
.attribute => |attr| try writer.writeAll(attr._value), .attribute => |attr| try writer.writeAll(attr._value.str()),
} }
} }
} }

View File

@@ -40,7 +40,7 @@ pub fn asNode(self: *Anchor) *Node {
pub fn getHref(self: *Anchor, page: *Page) ![]const u8 { pub fn getHref(self: *Anchor, page: *Page) ![]const u8 {
const element = self.asElement(); const element = self.asElement();
const href = element.getAttributeSafe("href") orelse return ""; const href = element.getAttributeSafe(comptime .wrap("href")) orelse return "";
if (href.len == 0) { if (href.len == 0) {
return ""; 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 { 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 { pub fn getTarget(self: *Anchor) []const u8 {
return self.asElement().getAttributeSafe("target") orelse ""; return self.asElement().getAttributeSafe(comptime .wrap("target")) orelse "";
} }
pub fn setTarget(self: *Anchor, value: []const u8, page: *Page) !void { 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 { 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 { pub fn getType(self: *Anchor) []const u8 {
return self.asElement().getAttributeSafe("type") orelse ""; return self.asElement().getAttributeSafe(comptime .wrap("type")) orelse "";
} }
pub fn setType(self: *Anchor, value: []const u8, page: *Page) !void { 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 { pub fn getName(self: *const Anchor) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
} }
pub fn setName(self: *Anchor, value: []const u8, page: *Page) !void { 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 { 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 { fn getResolvedHref(self: *Anchor, page: *Page) !?[:0]const u8 {
const href = self.asElement().getAttributeSafe("href") orelse return null; const href = self.asElement().getAttributeSafe(comptime .wrap("href")) orelse return null;
if (href.len == 0) { if (href.len == 0) {
return null; return null;
} }

View File

@@ -16,6 +16,8 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const String = @import("../../../../string.zig").String;
const js = @import("../../../js/js.zig"); const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.zig"); const Page = @import("../../../Page.zig");
@@ -27,16 +29,16 @@ const Audio = @This();
_proto: *Media, _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 node = try page.createElementNS(.html, "audio", null);
const el = node.as(Element); const el = node.as(Element);
const list = try el.getOrCreateAttributeList(page); const list = try el.getOrCreateAttributeList(page);
// Always set to "auto" initially. // 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. // Set URL if provided.
if (maybe_url) |url| { if (maybe_url) |url| {
_ = try list.putSafe("src", url, el, page); _ = try list.putSafe(comptime .wrap("src"), url, el, page);
} }
return node.as(Media); return node.as(Media);

View File

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

View File

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

View File

@@ -40,23 +40,23 @@ pub fn asNode(self: *Canvas) *Node {
} }
pub fn getWidth(self: *const Canvas) u32 { pub fn getWidth(self: *const Canvas) u32 {
const attr = self.asConstElement().getAttributeSafe("width") orelse return 300; const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 300;
return std.fmt.parseUnsigned(u32, attr, 10) catch 300; return std.fmt.parseUnsigned(u32, attr, 10) catch 300;
} }
pub fn setWidth(self: *Canvas, value: u32, page: *Page) !void { pub fn setWidth(self: *Canvas, value: u32, page: *Page) !void {
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); 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 { pub fn getHeight(self: *const Canvas) u32 {
const attr = self.asConstElement().getAttributeSafe("height") orelse return 150; const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 150;
return std.fmt.parseUnsigned(u32, attr, 10) catch 150; return std.fmt.parseUnsigned(u32, attr, 10) catch 150;
} }
pub fn setHeight(self: *Canvas, value: u32, page: *Page) !void { pub fn setHeight(self: *Canvas, value: u32, page: *Page) !void {
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); 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, /// Since there's no base class rendering contextes inherit from,

View File

@@ -64,7 +64,7 @@ pub fn invokeDisconnectedCallback(self: *Custom, page: *Page) void {
self.invokeCallback("disconnectedCallback", .{}, page); 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; const definition = self._definition orelse return;
if (!definition.isAttributeObserved(name)) { if (!definition.isAttributeObserved(name)) {
return; return;
@@ -144,7 +144,7 @@ pub fn invokeDisconnectedCallbackOnElement(element: *Element, page: *Page) void
invokeCallbackOnElement(element, definition, "disconnectedCallback", .{}, page); 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 // Autonomous custom element
if (element.is(Custom)) |custom| { if (element.is(Custom)) |custom| {
custom.invokeAttributeChangedCallback(name, old_value, new_value, page); 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 // Check if element has "is" attribute and attach customized built-in definition
pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void { pub fn checkAndAttachBuiltIn(element: *Element, page: *Page) !void {
const is_value = element.getAttributeSafe("is") orelse return; const is_value = element.getAttributeSafe(comptime .wrap("is")) orelse return;
const custom_elements = page.window.getCustomElements(); const custom_elements = page.window.getCustomElements();
const definition = custom_elements._definitions.get(is_value) orelse return; const definition = custom_elements._definitions.get(is_value) orelse return;

View File

@@ -36,11 +36,11 @@ pub fn asNode(self: *Data) *Node {
} }
pub fn getValue(self: *Data) []const u8 { pub fn getValue(self: *Data) []const u8 {
return self.asElement().getAttributeSafe("value") orelse ""; return self.asElement().getAttributeSafe(comptime .wrap("value")) orelse "";
} }
pub fn setValue(self: *Data, value: []const u8, page: *Page) !void { 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 { pub const JsApi = struct {

View File

@@ -20,23 +20,23 @@ pub fn asNode(self: *Dialog) *Node {
} }
pub fn getOpen(self: *const Dialog) bool { pub fn getOpen(self: *const Dialog) bool {
return self.asConstElement().getAttributeSafe("open") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("open")) != null;
} }
pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void { pub fn setOpen(self: *Dialog, open: bool, page: *Page) !void {
if (open) { if (open) {
try self.asElement().setAttributeSafe("open", "", page); try self.asElement().setAttributeSafe(comptime .wrap("open"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("open", page); try self.asElement().removeAttribute(comptime .wrap("open"), page);
} }
} }
pub fn getReturnValue(self: *const Dialog) []const u8 { pub fn getReturnValue(self: *const Dialog) []const u8 {
return self.asConstElement().getAttributeSafe("returnvalue") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("returnvalue")) orelse "";
} }
pub fn setReturnValue(self: *Dialog, value: []const u8, page: *Page) !void { 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 { pub const JsApi = struct {

View File

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

View File

@@ -15,11 +15,11 @@ pub fn constructor(w_: ?u32, h_: ?u32, page: *Page) !*Image {
if (w_) |w| blk: { if (w_) |w| blk: {
const w_string = std.fmt.bufPrint(&page.buf, "{d}", .{w}) catch break :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: { if (h_) |h| blk: {
const h_string = std.fmt.bufPrint(&page.buf, "{d}", .{h}) catch break :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); return el.as(Image);
} }
@@ -36,7 +36,7 @@ pub fn asNode(self: *Image) *Node {
pub fn getSrc(self: *const Image, page: *Page) ![]const u8 { pub fn getSrc(self: *const Image, page: *Page) ![]const u8 {
const element = self.asConstElement(); const element = self.asConstElement();
const src = element.getAttributeSafe("src") orelse return ""; const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
if (src.len == 0) { if (src.len == 0) {
return ""; 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 { 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 { pub fn getAlt(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("alt") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("alt")) orelse "";
} }
pub fn setAlt(self: *Image, value: []const u8, page: *Page) !void { 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 { pub fn getWidth(self: *const Image) u32 {
const attr = self.asConstElement().getAttributeSafe("width") orelse return 0; const attr = self.asConstElement().getAttributeSafe(comptime .wrap("width")) orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0; return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
} }
pub fn setWidth(self: *Image, value: u32, page: *Page) !void { pub fn setWidth(self: *Image, value: u32, page: *Page) !void {
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); 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 { pub fn getHeight(self: *const Image) u32 {
const attr = self.asConstElement().getAttributeSafe("height") orelse return 0; const attr = self.asConstElement().getAttributeSafe(comptime .wrap("height")) orelse return 0;
return std.fmt.parseUnsigned(u32, attr, 10) catch 0; return std.fmt.parseUnsigned(u32, attr, 10) catch 0;
} }
pub fn setHeight(self: *Image, value: u32, page: *Page) !void { pub fn setHeight(self: *Image, value: u32, page: *Page) !void {
const str = try std.fmt.allocPrint(page.call_arena, "{d}", .{value}); 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 { pub fn getCrossOrigin(self: *const Image) ?[]const u8 {
return self.asConstElement().getAttributeSafe("crossorigin"); return self.asConstElement().getAttributeSafe(comptime .wrap("crossorigin"));
} }
pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void { pub fn setCrossOrigin(self: *Image, value: ?[]const u8, page: *Page) !void {
if (value) |v| { 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 { pub fn getLoading(self: *const Image) []const u8 {
return self.asConstElement().getAttributeSafe("loading") orelse "eager"; return self.asConstElement().getAttributeSafe(comptime .wrap("loading")) orelse "eager";
} }
pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void { pub fn setLoading(self: *Image, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("loading", value, page); try self.asElement().setAttributeSafe(comptime .wrap("loading"), .wrap(value), page);
} }
pub const JsApi = struct { pub const JsApi = struct {

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const String = @import("../../../../string.zig").String;
const js = @import("../../../js/js.zig"); const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.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 { pub fn setType(self: *Input, typ: []const u8, page: *Page) !void {
// Setting the type property should update the attribute, which will trigger attributeChange // Setting the type property should update the attribute, which will trigger attributeChange
const type_enum = Type.fromString(typ); 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 { 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 { 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 { 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 { pub fn setDefaultChecked(self: *Input, checked: bool, page: *Page) !void {
if (checked) { if (checked) {
try self.asElement().setAttributeSafe("checked", "", page); try self.asElement().setAttributeSafe(comptime .wrap("checked"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("checked", page); try self.asElement().removeAttribute(comptime .wrap("checked"), page);
} }
} }
pub fn getDisabled(self: *const Input) bool { pub fn getDisabled(self: *const Input) bool {
// TODO: Also check for disabled fieldset ancestors // TODO: Also check for disabled fieldset ancestors
// (but not if we're inside a <legend> of that fieldset) // (but not if we're inside a <legend> of that fieldset)
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
} }
pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Input, disabled: bool, page: *Page) !void {
if (disabled) { if (disabled) {
try self.asElement().setAttributeSafe("disabled", "", page); try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("disabled", page); try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
} }
} }
pub fn getName(self: *const Input) []const u8 { pub fn getName(self: *const Input) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
} }
pub fn setName(self: *Input, name: []const u8, page: *Page) !void { 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 { pub fn getAccept(self: *const Input) []const u8 {
return self.asConstElement().getAttributeSafe("accept") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("accept")) orelse "";
} }
pub fn setAccept(self: *Input, accept: []const u8, page: *Page) !void { 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 { pub fn getAlt(self: *const Input) []const u8 {
return self.asConstElement().getAttributeSafe("alt") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("alt")) orelse "";
} }
pub fn setAlt(self: *Input, alt: []const u8, page: *Page) !void { 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 { pub fn getMaxLength(self: *const Input) i32 {
const attr = self.asConstElement().getAttributeSafe("maxlength") orelse return -1; const attr = self.asConstElement().getAttributeSafe(comptime .wrap("maxlength")) orelse return -1;
return std.fmt.parseInt(i32, attr, 10) catch -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; var buf: [32]u8 = undefined;
const value = std.fmt.bufPrint(&buf, "{d}", .{max_length}) catch unreachable; 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 { pub fn getSize(self: *const Input) i32 {
const attr = self.asConstElement().getAttributeSafe("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; const parsed = std.fmt.parseInt(i32, attr, 10) catch return 20;
return if (parsed == 0) 20 else parsed; return if (parsed == 0) 20 else parsed;
} }
@@ -216,46 +218,46 @@ pub fn setSize(self: *Input, size: i32, page: *Page) !void {
return error.ZeroNotAllowed; return error.ZeroNotAllowed;
} }
if (size < 0) { 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; var buf: [32]u8 = undefined;
const value = std.fmt.bufPrint(&buf, "{d}", .{size}) catch unreachable; 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 { pub fn getSrc(self: *const Input, page: *Page) ![]const u8 {
const src = self.asConstElement().getAttributeSafe("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 // If attribute is explicitly set (even if empty), resolve it against the base URL
return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{}); return @import("../../URL.zig").resolve(page.call_arena, page.base(), src, .{});
} }
pub fn setSrc(self: *Input, src: []const u8, page: *Page) !void { pub fn setSrc(self: *Input, src: []const u8, page: *Page) !void {
const trimmed = std.mem.trim(u8, src, &std.ascii.whitespace); 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 { pub fn getReadonly(self: *const Input) bool {
return self.asConstElement().getAttributeSafe("readonly") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("readonly")) != null;
} }
pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void { pub fn setReadonly(self: *Input, readonly: bool, page: *Page) !void {
if (readonly) { if (readonly) {
try self.asElement().setAttributeSafe("readonly", "", page); try self.asElement().setAttributeSafe(comptime .wrap("readonly"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("readonly", page); try self.asElement().removeAttribute(comptime .wrap("readonly"), page);
} }
} }
pub fn getRequired(self: *const Input) bool { pub fn getRequired(self: *const Input) bool {
return self.asConstElement().getAttributeSafe("required") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
} }
pub fn setRequired(self: *Input, required: bool, page: *Page) !void { pub fn setRequired(self: *Input, required: bool, page: *Page) !void {
if (required) { if (required) {
try self.asElement().setAttributeSafe("required", "", page); try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page);
} else { } 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(); const element = self.asElement();
// If form attribute exists, ONLY use that (even if it references nothing) // If form attribute exists, ONLY use that (even if it references nothing)
if (element.getAttributeSafe("form")) |form_id| { if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| {
if (page.document.getElementById(form_id, page)) |form_element| { if (page.document.getElementById(form_id, page)) |form_element| {
return form_element.is(Form); return form_element.is(Form);
} }
@@ -402,7 +404,7 @@ pub fn getForm(self: *Input, page: *Page) ?*Form {
fn uncheckRadioGroup(self: *Input, page: *Page) !void { fn uncheckRadioGroup(self: *Input, page: *Page) !void {
const element = self.asElement(); const element = self.asElement();
const name = element.getAttributeSafe("name") orelse return; const name = element.getAttributeSafe(comptime .wrap("name")) orelse return;
if (name.len == 0) { if (name.len == 0) {
return; return;
} }
@@ -420,7 +422,7 @@ fn uncheckRadioGroup(self: *Input, page: *Page) !void {
continue; continue;
} }
const other_name = other_element.getAttributeSafe("name") orelse continue; const other_name = other_element.getAttributeSafe(comptime .wrap("name")) orelse continue;
if (!std.mem.eql(u8, name, other_name)) { if (!std.mem.eql(u8, name, other_name)) {
continue; continue;
} }
@@ -481,14 +483,14 @@ pub const Build = struct {
const element = self.asElement(); const element = self.asElement();
// Store initial values from attributes // Store initial values from attributes
self._default_value = element.getAttributeSafe("value"); self._default_value = element.getAttributeSafe(comptime .wrap("value"));
self._default_checked = element.getAttributeSafe("checked") != null; self._default_checked = element.getAttributeSafe(comptime .wrap("checked")) != null;
// Current state starts equal to default // Current state starts equal to default
self._value = self._default_value; self._value = self._default_value;
self._checked = self._default_checked; self._checked = self._default_checked;
self._input_type = if (element.getAttributeSafe("type")) |type_attr| self._input_type = if (element.getAttributeSafe(comptime .wrap("type"))) |type_attr|
Type.fromString(type_attr) Type.fromString(type_attr)
else else
.text; .text;
@@ -499,12 +501,12 @@ pub const Build = struct {
} }
} }
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, page: *Page) !void { pub fn attributeChange(element: *Element, name: String, value: String, page: *Page) !void {
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return; const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return;
const self = element.as(Input); const self = element.as(Input);
switch (attribute) { switch (attribute) {
.type => self._input_type = Type.fromString(value), .type => self._input_type = Type.fromString(value.str()),
.value => self._default_value = value, .value => self._default_value = try page.arena.dupe(u8, value.str()),
.checked => { .checked => {
self._default_checked = true; self._default_checked = true;
// Only update checked state if it hasn't been manually modified // 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 { pub fn attributeRemove(element: *Element, name: String, _: *Page) !void {
const attribute = std.meta.stringToEnum(enum { type, value, checked }, name) orelse return; const attribute = std.meta.stringToEnum(enum { type, value, checked }, name.str()) orelse return;
const self = element.as(Input); const self = element.as(Input);
switch (attribute) { switch (attribute) {
.type => self._input_type = .text, .type => self._input_type = .text,

View File

@@ -36,7 +36,7 @@ pub fn asNode(self: *Link) *Node {
pub fn getHref(self: *Link, page: *Page) ![]const u8 { pub fn getHref(self: *Link, page: *Page) ![]const u8 {
const element = self.asElement(); const element = self.asElement();
const href = element.getAttributeSafe("href") orelse return ""; const href = element.getAttributeSafe(comptime .wrap("href")) orelse return "";
if (href.len == 0) { if (href.len == 0) {
return ""; 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 { 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 { pub fn getRel(self: *Link) []const u8 {
return self.asElement().getAttributeSafe("rel") orelse return ""; return self.asElement().getAttributeSafe(comptime .wrap("rel")) orelse return "";
} }
pub fn setRel(self: *Link, value: []const u8, page: *Page) !void { pub fn setRel(self: *Link, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("rel", value, page); try self.asElement().setAttributeSafe(comptime .wrap("rel"), .wrap(value), page);
} }
pub const JsApi = struct { pub const JsApi = struct {

View File

@@ -219,7 +219,7 @@ pub fn setCurrentTime(self: *Media, value: f64) void {
pub fn getSrc(self: *const Media, page: *Page) ![]const u8 { pub fn getSrc(self: *const Media, page: *Page) ![]const u8 {
const element = self.asConstElement(); const element = self.asConstElement();
const src = element.getAttributeSafe("src") orelse return ""; const src = element.getAttributeSafe(comptime .wrap("src")) orelse return "";
if (src.len == 0) { if (src.len == 0) {
return ""; 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 { 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 { pub fn getAutoplay(self: *const Media) bool {
return self.asConstElement().getAttributeSafe("autoplay") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("autoplay")) != null;
} }
pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void { pub fn setAutoplay(self: *Media, value: bool, page: *Page) !void {
if (value) { if (value) {
try self.asElement().setAttributeSafe("autoplay", "", page); try self.asElement().setAttributeSafe(comptime .wrap("autoplay"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("autoplay", page); try self.asElement().removeAttribute(comptime .wrap("autoplay"), page);
} }
} }
pub fn getControls(self: *const Media) bool { pub fn getControls(self: *const Media) bool {
return self.asConstElement().getAttributeSafe("controls") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("controls")) != null;
} }
pub fn setControls(self: *Media, value: bool, page: *Page) !void { pub fn setControls(self: *Media, value: bool, page: *Page) !void {
if (value) { if (value) {
try self.asElement().setAttributeSafe("controls", "", page); try self.asElement().setAttributeSafe(comptime .wrap("controls"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("controls", page); try self.asElement().removeAttribute(comptime .wrap("controls"), page);
} }
} }
pub fn getLoop(self: *const Media) bool { pub fn getLoop(self: *const Media) bool {
return self.asConstElement().getAttributeSafe("loop") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("loop")) != null;
} }
pub fn setLoop(self: *Media, value: bool, page: *Page) !void { pub fn setLoop(self: *Media, value: bool, page: *Page) !void {
if (value) { if (value) {
try self.asElement().setAttributeSafe("loop", "", page); try self.asElement().setAttributeSafe(comptime .wrap("loop"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("loop", page); try self.asElement().removeAttribute(comptime .wrap("loop"), page);
} }
} }
pub fn getPreload(self: *const Media) []const u8 { pub fn getPreload(self: *const Media) []const u8 {
return self.asConstElement().getAttributeSafe("preload") orelse "auto"; return self.asConstElement().getAttributeSafe(comptime .wrap("preload")) orelse "auto";
} }
pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void { pub fn setPreload(self: *Media, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("preload", value, page); try self.asElement().setAttributeSafe(comptime .wrap("preload"), .wrap(value), page);
} }
pub const JsApi = struct { pub const JsApi = struct {

View File

@@ -37,35 +37,35 @@ pub fn asNode(self: *Meta) *Node {
} }
pub fn getName(self: *Meta) []const u8 { pub fn getName(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("name") orelse return ""; return self.asElement().getAttributeSafe(comptime .wrap("name")) orelse return "";
} }
pub fn setName(self: *Meta, value: []const u8, page: *Page) !void { pub fn setName(self: *Meta, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("name", value, page); try self.asElement().setAttributeSafe(comptime .wrap("name"), .wrap(value), page);
} }
pub fn getHttpEquiv(self: *Meta) []const u8 { pub fn getHttpEquiv(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("http-equiv") orelse return ""; return self.asElement().getAttributeSafe(comptime .wrap("http-equiv")) orelse return "";
} }
pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void { pub fn setHttpEquiv(self: *Meta, value: []const u8, page: *Page) !void {
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 { pub fn getContent(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("content") orelse return ""; return self.asElement().getAttributeSafe(comptime .wrap("content")) orelse return "";
} }
pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void { pub fn setContent(self: *Meta, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("content", value, page); try self.asElement().setAttributeSafe(comptime .wrap("content"), .wrap(value), page);
} }
pub fn getMedia(self: *Meta) []const u8 { pub fn getMedia(self: *Meta) []const u8 {
return self.asElement().getAttributeSafe("media") orelse return ""; return self.asElement().getAttributeSafe(comptime .wrap("media")) orelse return "";
} }
pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void { pub fn setMedia(self: *Meta, value: []const u8, page: *Page) !void {
try self.asElement().setAttributeSafe("media", value, page); try self.asElement().setAttributeSafe(comptime .wrap("media"), .wrap(value), page);
} }
pub const JsApi = struct { pub const JsApi = struct {

View File

@@ -17,6 +17,8 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
const std = @import("std"); const std = @import("std");
const String = @import("../../../../string.zig").String;
const js = @import("../../../js/js.zig"); const js = @import("../../../js/js.zig");
const Page = @import("../../../Page.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 { pub fn setValue(self: *Option, value: []const u8, page: *Page) !void {
const owned = try page.dupeString(value); 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; self._value = owned;
} }
@@ -87,18 +89,18 @@ pub fn getDisabled(self: *const Option) bool {
pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Option, disabled: bool, page: *Page) !void {
self._disabled = disabled; self._disabled = disabled;
if (disabled) { if (disabled) {
try self.asElement().setAttributeSafe("disabled", "", page); try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("disabled", page); try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
} }
} }
pub fn getName(self: *const Option) []const u8 { pub fn getName(self: *const Option) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
} }
pub fn setName(self: *Option, name: []const u8, page: *Page) !void { 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 { pub const JsApi = struct {
@@ -124,21 +126,21 @@ pub const Build = struct {
const element = self.asElement(); const element = self.asElement();
// Check for value attribute // Check for value attribute
self._value = element.getAttributeSafe("value"); self._value = element.getAttributeSafe(comptime .wrap("value"));
// Check for selected attribute // Check for selected attribute
self._default_selected = element.getAttributeSafe("selected") != null; self._default_selected = element.getAttributeSafe(comptime .wrap("selected")) != null;
self._selected = self._default_selected; self._selected = self._default_selected;
// Check for disabled attribute // Check for disabled attribute
self._disabled = element.getAttributeSafe("disabled") != null; self._disabled = element.getAttributeSafe(comptime .wrap("disabled")) != null;
} }
pub fn attributeChange(element: *Element, name: []const u8, value: []const u8, _: *Page) !void { pub fn attributeChange(element: *Element, name: String, value: String, _: *Page) !void {
const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return; const attribute = std.meta.stringToEnum(enum { value, selected }, name.str()) orelse return;
const self = element.as(Option); const self = element.as(Option);
switch (attribute) { switch (attribute) {
.value => self._value = value, .value => self._value = value.str(),
.selected => { .selected => {
self._default_selected = true; self._default_selected = true;
self._selected = true; self._selected = true;
@@ -146,8 +148,8 @@ pub const Build = struct {
} }
} }
pub fn attributeRemove(element: *Element, name: []const u8, _: *Page) !void { pub fn attributeRemove(element: *Element, name: String, _: *Page) !void {
const attribute = std.meta.stringToEnum(enum { value, selected }, name) orelse return; const attribute = std.meta.stringToEnum(enum { value, selected }, name.str()) orelse return;
const self = element.as(Option); const self = element.as(Option);
switch (attribute) { switch (attribute) {
.value => self._value = null, .value => self._value = null,

View File

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

View File

@@ -121,39 +121,39 @@ pub fn setSelectedIndex(self: *Select, index: i32) !void {
} }
pub fn getMultiple(self: *const Select) bool { pub fn getMultiple(self: *const Select) bool {
return self.asConstElement().getAttributeSafe("multiple") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("multiple")) != null;
} }
pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void { pub fn setMultiple(self: *Select, multiple: bool, page: *Page) !void {
if (multiple) { if (multiple) {
try self.asElement().setAttributeSafe("multiple", "", page); try self.asElement().setAttributeSafe(comptime .wrap("multiple"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("multiple", page); try self.asElement().removeAttribute(comptime .wrap("multiple"), page);
} }
} }
pub fn getDisabled(self: *const Select) bool { pub fn getDisabled(self: *const Select) bool {
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
} }
pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Select, disabled: bool, page: *Page) !void {
if (disabled) { if (disabled) {
try self.asElement().setAttributeSafe("disabled", "", page); try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("disabled", page); try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
} }
} }
pub fn getName(self: *const Select) []const u8 { pub fn getName(self: *const Select) []const u8 {
return self.asConstElement().getAttributeSafe("name") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("name")) orelse "";
} }
pub fn setName(self: *Select, name: []const u8, page: *Page) !void { 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 { pub fn getSize(self: *const Select) u32 {
const s = self.asConstElement().getAttributeSafe("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); 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 { pub fn setSize(self: *Select, size: u32, page: *Page) !void {
const size_string = try std.fmt.allocPrint(page.call_arena, "{d}", .{size}); 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 { pub fn getRequired(self: *const Select) bool {
return self.asConstElement().getAttributeSafe("required") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("required")) != null;
} }
pub fn setRequired(self: *Select, required: bool, page: *Page) !void { pub fn setRequired(self: *Select, required: bool, page: *Page) !void {
if (required) { if (required) {
try self.asElement().setAttributeSafe("required", "", page); try self.asElement().setAttributeSafe(comptime .wrap("required"), .wrap(""), page);
} else { } 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(); const element = self.asElement();
// If form attribute exists, ONLY use that (even if it references nothing) // If form attribute exists, ONLY use that (even if it references nothing)
if (element.getAttributeSafe("form")) |form_id| { if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| {
if (page.document.getElementById(form_id, page)) |form_element| { if (page.document.getElementById(form_id, page)) |form_element| {
return form_element.is(Form); return form_element.is(Form);
} }

View File

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

View File

@@ -39,38 +39,38 @@ pub fn asNode(self: *Style) *Node {
// Attribute-backed properties // Attribute-backed properties
pub fn getBlocking(self: *const Style) []const u8 { pub fn getBlocking(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe("blocking") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("blocking")) orelse "";
} }
pub fn setBlocking(self: *Style, value: []const u8, page: *Page) !void { 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 { pub fn getMedia(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe("media") orelse ""; return self.asConstElement().getAttributeSafe(comptime .wrap("media")) orelse "";
} }
pub fn setMedia(self: *Style, value: []const u8, page: *Page) !void { 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 { pub fn getType(self: *const Style) []const u8 {
return self.asConstElement().getAttributeSafe("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 { 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 { pub fn getDisabled(self: *const Style) bool {
return self.asConstElement().getAttributeSafe("disabled") != null; return self.asConstElement().getAttributeSafe(comptime .wrap("disabled")) != null;
} }
pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void { pub fn setDisabled(self: *Style, disabled: bool, page: *Page) !void {
if (disabled) { if (disabled) {
try self.asElement().setAttributeSafe("disabled", "", page); try self.asElement().setAttributeSafe(comptime .wrap("disabled"), .wrap(""), page);
} else { } else {
try self.asElement().removeAttribute("disabled", page); try self.asElement().removeAttribute(comptime .wrap("disabled"), page);
} }
} }

View File

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

View File

@@ -53,7 +53,7 @@ pub fn getVideoHeight(_: *const Video) u32 {
pub fn getPoster(self: *const Video, page: *Page) ![]const u8 { pub fn getPoster(self: *const Video, page: *Page) ![]const u8 {
const element = self.asConstElement(); const element = self.asConstElement();
const poster = element.getAttributeSafe("poster") orelse return ""; const poster = element.getAttributeSafe(comptime .wrap("poster")) orelse return "";
if (poster.len == 0) { if (poster.len == 0) {
return ""; 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 { 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 { pub const JsApi = struct {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -344,7 +344,7 @@ test "cdp Node: Registry register" {
var doc = page.window._document; var doc = page.window._document;
{ {
const dom_node = (try doc.querySelector("#a1", page)).?.asNode(); const dom_node = (try doc.querySelector(.wrap("#a1"), page)).?.asNode();
const node = try registry.register(dom_node); const node = try registry.register(dom_node);
const n1b = registry.lookup_by_id.get(1).?; const n1b = registry.lookup_by_id.get(1).?;
const n1c = registry.lookup_by_node.get(node.dom).?; const n1c = registry.lookup_by_node.get(node.dom).?;
@@ -356,7 +356,7 @@ test "cdp Node: Registry register" {
} }
{ {
const dom_node = (try doc.querySelector("p", page)).?.asNode(); const dom_node = (try doc.querySelector(.wrap ("p"), page)).?.asNode();
const node = try registry.register(dom_node); const node = try registry.register(dom_node);
const n1b = registry.lookup_by_id.get(2).?; const n1b = registry.lookup_by_id.get(2).?;
const n1c = registry.lookup_by_node.get(node.dom).?; const n1c = registry.lookup_by_node.get(node.dom).?;
@@ -400,18 +400,18 @@ test "cdp Node: search list" {
defer page._session.removePage(); defer page._session.removePage();
var doc = page.window._document; var doc = page.window._document;
const s1 = try search_list.create((try doc.querySelectorAll("a", page))._nodes); const s1 = try search_list.create((try doc.querySelectorAll(.wrap ("a"), page))._nodes);
try testing.expectEqual("1", s1.name); try testing.expectEqual("1", s1.name);
try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids); try testing.expectEqualSlices(u32, &.{ 1, 2 }, s1.node_ids);
try testing.expectEqual(2, registry.lookup_by_id.count()); try testing.expectEqual(2, registry.lookup_by_id.count());
try testing.expectEqual(2, registry.lookup_by_node.count()); try testing.expectEqual(2, registry.lookup_by_node.count());
const s2 = try search_list.create((try doc.querySelectorAll("#a1", page))._nodes); const s2 = try search_list.create((try doc.querySelectorAll(.wrap ("#a1"), page))._nodes);
try testing.expectEqual("2", s2.name); try testing.expectEqual("2", s2.name);
try testing.expectEqualSlices(u32, &.{1}, s2.node_ids); try testing.expectEqualSlices(u32, &.{1}, s2.node_ids);
const s3 = try search_list.create((try doc.querySelectorAll("#a2", page))._nodes); const s3 = try search_list.create((try doc.querySelectorAll(.wrap ("#a2"), page))._nodes);
try testing.expectEqual("3", s3.name); try testing.expectEqual("3", s3.name);
try testing.expectEqualSlices(u32, &.{2}, s3.node_ids); try testing.expectEqualSlices(u32, &.{2}, s3.node_ids);

View File

@@ -33,6 +33,7 @@ pub const expectEqual = base.expectEqual;
pub const expectError = base.expectError; pub const expectError = base.expectError;
pub const expectEqualSlices = base.expectEqualSlices; pub const expectEqualSlices = base.expectEqualSlices;
pub const pageTest = base.pageTest; pub const pageTest = base.pageTest;
pub const newString = base.newString;
const Client = struct { const Client = struct {
allocator: Allocator, allocator: Allocator,

View File

@@ -271,17 +271,21 @@ pub const Header = struct {
}; };
pub const Headers = struct { pub const Headers = struct {
headers: *c.curl_slist, headers: ?*c.curl_slist,
cookies: ?[*c]const u8, cookies: ?[*c]const u8,
pub fn init(user_agent: [:0]const u8) !Headers { pub fn init(user_agent: [:0]const u8) !Headers {
const header_list = c.curl_slist_append(null, user_agent); const header_list = c.curl_slist_append(null, user_agent);
if (header_list == null) return error.OutOfMemory; if (header_list == null) {
return error.OutOfMemory;
}
return .{ .headers = header_list, .cookies = null }; return .{ .headers = header_list, .cookies = null };
} }
pub fn deinit(self: *const Headers) void { pub fn deinit(self: *const Headers) void {
c.curl_slist_free_all(self.headers); if (self.headers) |hdr| {
c.curl_slist_free_all(hdr);
}
} }
pub fn add(self: *Headers, header: [*c]const u8) !void { pub fn add(self: *Headers, header: [*c]const u8) !void {

View File

@@ -20,6 +20,8 @@ const std = @import("std");
const js = @import("browser/js/js.zig"); const js = @import("browser/js/js.zig");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const M = @This();
// German-string (small string optimization) // German-string (small string optimization)
pub const String = packed struct { pub const String = packed struct {
len: i32, len: i32,
@@ -34,6 +36,48 @@ pub const String = packed struct {
pub const empty = String{ .len = 0, .payload = .{ .content = @splat(0) } }; pub const empty = String{ .len = 0, .payload = .{ .content = @splat(0) } };
pub const deleted = String{ .len = tombstone, .payload = .{ .content = @splat(0) } }; pub const deleted = String{ .len = tombstone, .payload = .{ .content = @splat(0) } };
// 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) {
@compileError("Comptime string must be <= 12 bytes (SSO only): " ++ input);
}
var content: [12]u8 = @splat(0);
@memcpy(content[0..l], input);
return .{ .len = @intCast(l), .payload = .{ .content = content } };
}
// Runtime path - handle both String and []const u8
if (@TypeOf(input) == String) {
return input;
}
const l = input.len;
if (l <= 12) {
var content: [12]u8 = @splat(0);
@memcpy(content[0..l], input);
return .{ .len = @intCast(l), .payload = .{ .content = content } };
}
return .{
.len = @intCast(l),
.payload = .{ .heap = .{
.prefix = input[0..4].*,
.ptr = input.ptr,
} },
};
}
pub const InitOpts = struct { pub const InitOpts = struct {
dupe: bool = true, dupe: bool = true,
}; };
@@ -47,13 +91,11 @@ pub const String = packed struct {
@memcpy(content[0..l], input); @memcpy(content[0..l], input);
return .{ .len = @intCast(l), .payload = .{ .content = content } }; return .{ .len = @intCast(l), .payload = .{ .content = content } };
} }
var prefix: [4]u8 = @splat(0);
@memcpy(&prefix, input[0..4]);
return .{ return .{
.len = @intCast(l), .len = @intCast(l),
.payload = .{ .heap = .{ .payload = .{ .heap = .{
.prefix = prefix, .prefix = input[0..4].*,
.ptr = (intern(input) orelse (if (opts.dupe) (try allocator.dupe(u8, input)) else input)).ptr, .ptr = (intern(input) orelse (if (opts.dupe) (try allocator.dupe(u8, input)) else input)).ptr,
} }, } },
}; };
@@ -66,9 +108,8 @@ pub const String = packed struct {
} }
} }
pub fn fromJS(allocator: Allocator, js_obj: js.Object) !String { pub fn dupe(self: *const String, allocator: Allocator) !String {
const js_str = js_obj.toString(); return .init(allocator, self.str(), .{ .dupe = true });
return init(allocator, js_str, .{});
} }
pub fn str(self: *const String) []const u8 { pub fn str(self: *const String) []const u8 {
@@ -96,20 +137,22 @@ pub const String = packed struct {
} }
pub fn eql(a: String, b: String) bool { pub fn eql(a: String, b: String) bool {
if (a.len != b.len or a.len < 0 or b.len < 0) { if (@as(*const u64, @ptrCast(&a)).* != @as(*const u64, @ptrCast(&b)).*) {
return false; return false;
} }
if (a.len <= 12) { const len = a.len;
if (len < 0 or b.len < 0) {
return false;
}
if (len <= 12) {
return @reduce(.And, a.payload.content == b.payload.content); return @reduce(.And, a.payload.content == b.payload.content);
} }
if (@reduce(.And, a.payload.heap.prefix == b.payload.heap.prefix) == false) { // a.len == b.len at this point
return false; const al: usize = @intCast(len);
} const bl: usize = @intCast(len);
const al: usize = @intCast(a.len);
const bl: usize = @intCast(a.len);
return std.mem.eql(u8, a.payload.heap.ptr[0..al], b.payload.heap.ptr[0..bl]); return std.mem.eql(u8, a.payload.heap.ptr[0..al], b.payload.heap.ptr[0..bl]);
} }
@@ -188,6 +231,13 @@ pub const String = packed struct {
} }
}; };
// Discriminatory type that signals the bridge to use arena instead of call_arena
// Use this for strings that need to persist beyond the current call
// The caller can unwrap and store just the underlying .str field
pub const Global = struct {
str: String,
};
fn asUint(comptime string: anytype) std.meta.Int( fn asUint(comptime string: anytype) std.meta.Int(
.unsigned, .unsigned,
@bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0 @bitSizeOf(@TypeOf(string.*)) - 8, // (- 8) to exclude sentinel 0

View File

@@ -176,6 +176,11 @@ pub fn print(comptime fmt: []const u8, args: anytype) void {
} }
} }
const String = @import("string.zig").String;
pub fn newString(str: []const u8) String {
return String.init(arena_allocator, str, .{}) catch unreachable;
}
pub const Random = struct { pub const Random = struct {
var instance: ?std.Random.DefaultPrng = null; var instance: ?std.Random.DefaultPrng = null;