mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +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>
|
||||||
|
|
||||||
|
<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>
|
<script id=item>
|
||||||
{
|
{
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
@@ -166,6 +189,29 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</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>
|
<script id=errors>
|
||||||
{
|
{
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
|
|||||||
@@ -674,6 +674,11 @@ pub fn getClassList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
|||||||
return gop.value_ptr.*;
|
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 {
|
pub fn getRelList(self: *Element, page: *Page) !*collections.DOMTokenList {
|
||||||
const gop = try page._element_rel_lists.getOrPut(page.arena, self);
|
const gop = try page._element_rel_lists.getOrPut(page.arena, self);
|
||||||
if (!gop.found_existing) {
|
if (!gop.found_existing) {
|
||||||
@@ -1449,7 +1454,7 @@ pub const JsApi = struct {
|
|||||||
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{});
|
pub const slot = bridge.accessor(Element.getSlot, Element.setSlot, .{});
|
||||||
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{});
|
pub const dir = bridge.accessor(Element.getDir, Element.setDir, .{});
|
||||||
pub const className = bridge.accessor(Element.getClassName, Element.setClassName, .{});
|
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 dataset = bridge.accessor(Element.getDataset, null, .{});
|
||||||
pub const style = bridge.accessor(Element.getStyle, null, .{});
|
pub const style = bridge.accessor(Element.getStyle, null, .{});
|
||||||
pub const attributes = bridge.accessor(Element.getAttributeNamedNodeMap, 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 {
|
pub fn replace(self: *DOMTokenList, old_token: []const u8, new_token: []const u8, page: *Page) !bool {
|
||||||
try validateToken(old_token);
|
// Validate in spec order: both empty first, then both whitespace
|
||||||
try validateToken(new_token);
|
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);
|
var lookup = try self.getTokens(page);
|
||||||
if (lookup.contains(new_token)) {
|
|
||||||
if (std.mem.eql(u8, new_token, old_token) == false) {
|
// Check if old_token exists
|
||||||
_ = lookup.orderedRemove(old_token);
|
if (!lookup.contains(old_token)) {
|
||||||
try self.updateAttribute(lookup, page);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const key_ptr = lookup.getKeyPtr(old_token) orelse return false;
|
const allocator = page.call_arena;
|
||||||
key_ptr.* = new_token;
|
// Build new token list preserving order but replacing old with new
|
||||||
try self.updateAttribute(lookup, page);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +264,16 @@ 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());
|
if (tokens.count() > 0) {
|
||||||
try self._element.setAttribute(self._attribute_name, .wrap(joined), page);
|
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 {
|
const Iterator = struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user