mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 12:44:43 +00:00
Merge pull request #1531 from lightpanda-io/dom_token_list_compat
Improve compliance of DOMTokenList
This commit is contained in:
@@ -93,6 +93,29 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=replace_errors>
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
div.className = 'foo bar';
|
||||
|
||||
testing.withError((err) => {
|
||||
testing.expectEqual('SyntaxError', err.name);
|
||||
}, () => div.classList.replace('', 'baz'));
|
||||
|
||||
testing.withError((err) => {
|
||||
testing.expectEqual('SyntaxError', err.name);
|
||||
}, () => div.classList.replace('foo', ''));
|
||||
|
||||
testing.withError((err) => {
|
||||
testing.expectEqual('InvalidCharacterError', err.name);
|
||||
}, () => div.classList.replace('foo bar', 'baz'));
|
||||
|
||||
testing.withError((err) => {
|
||||
testing.expectEqual('InvalidCharacterError', err.name);
|
||||
}, () => div.classList.replace('foo', 'bar baz'));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=item>
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
@@ -166,6 +189,29 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=classList_assignment>
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
|
||||
// Direct assignment should work (equivalent to classList.value = ...)
|
||||
div.classList = 'foo bar baz';
|
||||
testing.expectEqual('foo bar baz', div.className);
|
||||
testing.expectEqual(3, div.classList.length);
|
||||
testing.expectEqual(true, div.classList.contains('foo'));
|
||||
|
||||
// Assigning again should replace
|
||||
div.classList = 'qux';
|
||||
testing.expectEqual('qux', div.className);
|
||||
testing.expectEqual(1, div.classList.length);
|
||||
testing.expectEqual(false, div.classList.contains('foo'));
|
||||
|
||||
// Empty assignment
|
||||
div.classList = '';
|
||||
testing.expectEqual('', div.className);
|
||||
testing.expectEqual(0, div.classList.length);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=errors>
|
||||
{
|
||||
const div = document.createElement('div');
|
||||
|
||||
@@ -674,6 +674,11 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||
return gop.value_ptr.*;
|
||||
}
|
||||
|
||||
pub fn setClassList(self: *Element, value: String, page: *Page) !void {
|
||||
const class_list = try self.getClassList(page);
|
||||
try class_list.setValue(value, page);
|
||||
}
|
||||
|
||||
pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||
const gop = try page._element_rel_lists.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
@@ -1449,7 +1454,7 @@ pub const JsApi = struct {
|
||||
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{});
|
||||
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{});
|
||||
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
||||
pub const classList = bridge.accessor(Element.getClassList, null, .{});
|
||||
pub const classList = bridge.accessor(Element.getClassList, Element.setClassList, .{});
|
||||
pub const dataset = bridge.accessor(Element.getDataset, null, .{});
|
||||
pub const style = bridge.accessor(Element.getStyle, null, .{});
|
||||
pub const attributes = bridge.accessor(Element.getAttributeNamedNodeMap, null, .{});
|
||||
|
||||
@@ -138,21 +138,59 @@ pub fn toggle(self: *DOMTokenList, token: []const u8, force: ?bool, page: *Page)
|
||||
}
|
||||
|
||||
pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8, page: *Page) !bool {
|
||||
try validateToken(old_token);
|
||||
try validateToken(new_token);
|
||||
// Validate in spec order: both empty first, then both whitespace
|
||||
if (old_token.len == 0 or new_token.len == 0) {
|
||||
return error.SyntaxError;
|
||||
}
|
||||
if (std.mem.indexOfAny(u8, old_token, WHITESPACE) != null) {
|
||||
return error.InvalidCharacterError;
|
||||
}
|
||||
if (std.mem.indexOfAny(u8, new_token, WHITESPACE) != null) {
|
||||
return error.InvalidCharacterError;
|
||||
}
|
||||
|
||||
var lookup = try self.getTokens(page);
|
||||
if (lookup.contains(new_token)) {
|
||||
if (std.mem.eql(u8, new_token, old_token) == false) {
|
||||
_ = lookup.orderedRemove(old_token);
|
||||
try self.updateAttribute(lookup, page);
|
||||
}
|
||||
|
||||
// Check if old_token exists
|
||||
if (!lookup.contains(old_token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If replacing with the same token, still need to trigger mutation
|
||||
if (std.mem.eql(u8, new_token, old_token)) {
|
||||
try self.updateAttribute(lookup, page);
|
||||
return true;
|
||||
}
|
||||
|
||||
const key_ptr = lookup.getKeyPtr(old_token) orelse return false;
|
||||
key_ptr.* = new_token;
|
||||
try self.updateAttribute(lookup, page);
|
||||
const allocator = page.call_arena;
|
||||
// Build new token list preserving order but replacing old with new
|
||||
var new_tokens = try std.ArrayList([]const u8).initCapacity(allocator, lookup.count());
|
||||
var replaced_old = false;
|
||||
|
||||
for (lookup.keys()) |token| {
|
||||
if (std.mem.eql(u8, token, old_token) and !replaced_old) {
|
||||
new_tokens.appendAssumeCapacity(new_token);
|
||||
replaced_old = true;
|
||||
} else if (std.mem.eql(u8, token, old_token)) {
|
||||
// Subsequent occurrences of old_token: skip (remove duplicates)
|
||||
continue;
|
||||
} else if (std.mem.eql(u8, token, new_token) and replaced_old) {
|
||||
// Occurrence of new_token AFTER replacement: skip (remove duplicate)
|
||||
continue;
|
||||
} else {
|
||||
// Any other token (including new_token before replacement): keep it
|
||||
new_tokens.appendAssumeCapacity(token);
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild lookup
|
||||
var new_lookup: Lookup = .empty;
|
||||
try new_lookup.ensureTotalCapacity(allocator, new_tokens.items.len);
|
||||
for (new_tokens.items) |token| {
|
||||
try new_lookup.put(allocator, token, {});
|
||||
}
|
||||
|
||||
try self.updateAttribute(new_lookup, page);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -226,8 +264,16 @@ fn validateToken(token: []const u8) !void {
|
||||
}
|
||||
|
||||
fn updateAttribute(self: *DOMTokenList, tokens: Lookup, page: *Page) !void {
|
||||
const joined = try std.mem.join(page.call_arena, " ", tokens.keys());
|
||||
try self._element.setAttribute(self._attribute_name, .wrap(joined), page);
|
||||
if (tokens.count() > 0) {
|
||||
const joined = try std.mem.join(page.call_arena, " ", tokens.keys());
|
||||
return self._element.setAttribute(self._attribute_name, .wrap(joined), page);
|
||||
}
|
||||
|
||||
// Only remove attribute if it didn't exist before (was null)
|
||||
// If it existed (even as ""), set it to "" to preserve its existence
|
||||
if (self._element.hasAttributeSafe(self._attribute_name)) {
|
||||
try self._element.setAttribute(self._attribute_name, .wrap(""), page);
|
||||
}
|
||||
}
|
||||
|
||||
const Iterator = struct {
|
||||
|
||||
Reference in New Issue
Block a user