Change dataset to work directly off DOM element

This commit is contained in:
Karl Seguin
2025-06-25 12:15:18 +08:00
parent 2aa5eb85ad
commit ec92f110b3
2 changed files with 41 additions and 54 deletions

View File

@@ -16,64 +16,66 @@
// 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 std = @import("std"); const std = @import("std");
const parser = @import("../netsurf.zig");
const Page = @import("../page.zig").Page; const Page = @import("../page.zig").Page;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const DataSet = @This(); const DataSet = @This();
attributes: std.StringHashMapUnmanaged([]const u8), element: *parser.Element,
pub const empty: DataSet = .{
.attributes = .empty,
};
const GetResult = union(enum) { const GetResult = union(enum) {
value: []const u8, value: []const u8,
undefined: void, undefined: void,
}; };
pub fn named_get(self: *const DataSet, name: []const u8, _: *bool) GetResult { pub fn named_get(self: *const DataSet, name: []const u8, _: *bool, page: *Page) !GetResult {
if (self.attributes.get(name)) |value| { const normalized_name = try normalize(page.call_arena, name);
if (try parser.elementGetAttribute(self.element, normalized_name)) |value| {
return .{ .value = value }; return .{ .value = value };
} }
return .{ .undefined = {} }; return .{ .undefined = {} };
} }
pub fn named_set(self: *DataSet, name: []const u8, value: []const u8, _: *bool, page: *Page) !void { pub fn named_set(self: *DataSet, name: []const u8, value: []const u8, _: *bool, page: *Page) !void {
const arena = page.arena; const normalized_name = try normalize(page.call_arena, name);
const gop = try self.attributes.getOrPut(arena, name); try parser.elementSetAttribute(self.element, normalized_name, value);
errdefer _ = self.attributes.remove(name);
if (!gop.found_existing) {
gop.key_ptr.* = try arena.dupe(u8, name);
}
gop.value_ptr.* = try arena.dupe(u8, value);
} }
pub fn named_delete(self: *DataSet, name: []const u8, _: *bool) void { pub fn named_delete(self: *DataSet, name: []const u8, _: *bool, page: *Page) !void {
_ = self.attributes.remove(name); const normalized_name = try normalize(page.call_arena, name);
try parser.elementRemoveAttribute(self.element, normalized_name);
} }
pub fn normalizeName(allocator: Allocator, name: []const u8) ![]const u8 { fn normalize(allocator: Allocator, name: []const u8) ![]const u8 {
std.debug.assert(std.mem.startsWith(u8, name, "data-")); var upper_count: usize = 0;
var owned = try allocator.alloc(u8, name.len - 5); for (name) |c| {
if (std.ascii.isUpper(c)) {
var pos: usize = 0; upper_count += 1;
var capitalize = false;
for (name[5..]) |c| {
if (c == '-') {
capitalize = true;
continue;
} }
}
// for every upper-case letter, we'll probably need a dash before it
// and we need the 'data-' prefix
var normalized = try allocator.alloc(u8, name.len + upper_count + 5);
if (capitalize) { @memcpy(normalized[0..5], "data-");
capitalize = false; if (upper_count == 0) {
owned[pos] = std.ascii.toUpper(c); @memcpy(normalized[5..], name);
return normalized;
}
var pos: usize = 5;
for (name) |c| {
if (std.ascii.isUpper(c)) {
normalized[pos] = '-';
pos += 1;
normalized[pos] = c + 32;
} else { } else {
owned[pos] = c; normalized[pos] = c;
} }
pos += 1; pos += 1;
} }
return owned[0..pos]; return normalized;
} }
const testing = @import("../../testing.zig"); const testing = @import("../../testing.zig");
@@ -88,5 +90,11 @@ test "Browser.HTML.DataSet" {
.{ "delete el1.dataset.x", "true" }, .{ "delete el1.dataset.x", "true" },
.{ "el1.dataset.x", "undefined" }, .{ "el1.dataset.x", "undefined" },
.{ "delete el1.dataset.other", "true" }, // yes, this is right .{ "delete el1.dataset.other", "true" }, // yes, this is right
.{ "let ds1 = el1.dataset", null },
.{ "ds1.helloWorld = 'yes'", null },
.{ "el1.getAttribute('data-hello-world')", "yes" },
.{ "el1.setAttribute('data-this-will-work', 'positive')", null },
.{ "ds1.thisWillWork", "positive" },
}, .{}); }, .{});
} }

View File

@@ -128,28 +128,7 @@ pub const HTMLElement = struct {
if (state.dataset) |*ds| { if (state.dataset) |*ds| {
return ds; return ds;
} }
state.dataset = DataSet{ .element = @ptrCast(e) };
// The first time this is called, load the data attributes from the DOM
var ds: DataSet = .empty;
if (try parser.nodeGetAttributes(@ptrCast(e))) |map| {
const arena = page.arena;
const count = try parser.namedNodeMapGetLength(map);
for (0..count) |i| {
const attr = try parser.namedNodeMapItem(map, @intCast(i)) orelse continue;
const name = try parser.attributeGetName(attr);
if (!std.mem.startsWith(u8, name, "data-")) {
continue;
}
const normalized_name = try DataSet.normalizeName(arena, name);
const value = try parser.attributeGetValue(attr) orelse "";
// I don't think we need to dupe value, It'll live in libdom for
// as long as the page due to the fact that we're using an arena.
try ds.attributes.put(arena, normalized_name, value);
}
}
state.dataset = ds;
return &state.dataset.?; return &state.dataset.?;
} }