diff --git a/src/browser/html/DataSet.zig b/src/browser/html/DataSet.zig index 40ba0417..df17c9dc 100644 --- a/src/browser/html/DataSet.zig +++ b/src/browser/html/DataSet.zig @@ -16,64 +16,66 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . const std = @import("std"); +const parser = @import("../netsurf.zig"); const Page = @import("../page.zig").Page; + const Allocator = std.mem.Allocator; const DataSet = @This(); -attributes: std.StringHashMapUnmanaged([]const u8), - -pub const empty: DataSet = .{ - .attributes = .empty, -}; +element: *parser.Element, const GetResult = union(enum) { value: []const u8, undefined: void, }; -pub fn named_get(self: *const DataSet, name: []const u8, _: *bool) GetResult { - if (self.attributes.get(name)) |value| { +pub fn named_get(self: *const DataSet, name: []const u8, _: *bool, page: *Page) !GetResult { + const normalized_name = try normalize(page.call_arena, name); + if (try parser.elementGetAttribute(self.element, normalized_name)) |value| { return .{ .value = value }; } return .{ .undefined = {} }; } pub fn named_set(self: *DataSet, name: []const u8, value: []const u8, _: *bool, page: *Page) !void { - const arena = page.arena; - const gop = try self.attributes.getOrPut(arena, name); - 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); + const normalized_name = try normalize(page.call_arena, name); + try parser.elementSetAttribute(self.element, normalized_name, value); } -pub fn named_delete(self: *DataSet, name: []const u8, _: *bool) void { - _ = self.attributes.remove(name); +pub fn named_delete(self: *DataSet, name: []const u8, _: *bool, page: *Page) !void { + 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 { - std.debug.assert(std.mem.startsWith(u8, name, "data-")); - var owned = try allocator.alloc(u8, name.len - 5); - - var pos: usize = 0; - var capitalize = false; - for (name[5..]) |c| { - if (c == '-') { - capitalize = true; - continue; +fn normalize(allocator: Allocator, name: []const u8) ![]const u8 { + var upper_count: usize = 0; + for (name) |c| { + if (std.ascii.isUpper(c)) { + upper_count += 1; } + } + // 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) { - capitalize = false; - owned[pos] = std.ascii.toUpper(c); + @memcpy(normalized[0..5], "data-"); + if (upper_count == 0) { + @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 { - owned[pos] = c; + normalized[pos] = c; } pos += 1; } - return owned[0..pos]; + return normalized; } const testing = @import("../../testing.zig"); @@ -88,5 +90,11 @@ test "Browser.HTML.DataSet" { .{ "delete el1.dataset.x", "true" }, .{ "el1.dataset.x", "undefined" }, .{ "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" }, }, .{}); } diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index a0ff8568..40a3b2b7 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -128,28 +128,7 @@ pub const HTMLElement = struct { if (state.dataset) |*ds| { return ds; } - - // 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; + state.dataset = DataSet{ .element = @ptrCast(e) }; return &state.dataset.?; }