diff --git a/src/browser/tests/range.html b/src/browser/tests/range.html index 73a43ff7..c1fb901f 100644 --- a/src/browser/tests/range.html +++ b/src/browser/tests/range.html @@ -820,3 +820,137 @@ }); } + + + + + + + + + + diff --git a/src/browser/webapi/MutationObserver.zig b/src/browser/webapi/MutationObserver.zig index 9e1acbdc..5e066d17 100644 --- a/src/browser/webapi/MutationObserver.zig +++ b/src/browser/webapi/MutationObserver.zig @@ -69,6 +69,10 @@ pub fn observe(self: *MutationObserver, target: *Node, options: ObserveOptions, copied_options.attributeFilter = filter_copy; } + if (options.characterDataOldValue) { + copied_options.characterData = true; + } + // Check if already observing this target for (self._observing.items) |*obs| { if (obs.target == target) { @@ -274,6 +278,13 @@ pub const MutationRecord = struct { return self._target; } + pub fn getAttributeNamespace(self: *const MutationRecord) ?[]const u8 { + if (self._attribute_name != null) { + return "http://www.w3.org/1999/xhtml"; + } + return null; + } + pub fn getAttributeName(self: *const MutationRecord) ?[]const u8 { return self._attribute_name; } @@ -310,6 +321,7 @@ pub const MutationRecord = struct { pub const @"type" = bridge.accessor(MutationRecord.getType, null, .{}); pub const target = bridge.accessor(MutationRecord.getTarget, null, .{}); pub const attributeName = bridge.accessor(MutationRecord.getAttributeName, null, .{}); + pub const attributeNamespace = bridge.accessor(MutationRecord.getAttributeNamespace, null, .{}); pub const oldValue = bridge.accessor(MutationRecord.getOldValue, null, .{}); pub const addedNodes = bridge.accessor(MutationRecord.getAddedNodes, null, .{}); pub const removedNodes = bridge.accessor(MutationRecord.getRemovedNodes, null, .{}); diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 03f7ac7f..b8bb1efd 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -652,9 +652,9 @@ pub fn getData(self: *const Node) []const u8 { }; } -pub fn setData(self: *Node, data: []const u8) void { +pub fn setData(self: *Node, data: []const u8, page: *Page) !void { switch (self._type) { - .cdata => |c| c._data = data, + .cdata => |c| try c.setData(data, page), else => {}, } } diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index 54f4f5a4..e8d08b84 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -357,7 +357,7 @@ pub fn deleteContents(self: *Range, page: *Page) !void { u8, &.{ text_data[0..self._proto._start_offset], text_data[self._proto._end_offset..] }, ); - self._proto._start_container.setData(new_text); + try self._proto._start_container.setData(new_text, page); } else { // Delete child nodes in range var offset = self._proto._start_offset; @@ -371,8 +371,43 @@ pub fn deleteContents(self: *Range, page: *Page) !void { return; } - // Complex case: different containers - simplified implementation - // Just collapse the range for now + // Complex case: different containers + // Handle start container - if it's a text node, truncate it + if (self._proto._start_container.is(Node.CData)) |_| { + const text_data = self._proto._start_container.getData(); + if (self._proto._start_offset < text_data.len) { + // Keep only the part before start_offset + const new_text = text_data[0..self._proto._start_offset]; + try self._proto._start_container.setData(new_text, page); + } + } + + // Handle end container - if it's a text node, truncate it + if (self._proto._end_container.is(Node.CData)) |_| { + const text_data = self._proto._end_container.getData(); + if (self._proto._end_offset < text_data.len) { + // Keep only the part from end_offset onwards + const new_text = text_data[self._proto._end_offset..]; + try self._proto._end_container.setData(new_text, page); + } else if (self._proto._end_offset == text_data.len) { + // If we're at the end, set to empty (will be removed if needed) + try self._proto._end_container.setData("", page); + } + } + + // Remove nodes between start and end containers + // For now, handle the common case where they're siblings + if (self._proto._start_container.parentNode() == self._proto._end_container.parentNode()) { + var current = self._proto._start_container.nextSibling(); + while (current != null and current != self._proto._end_container) { + const next = current.?.nextSibling(); + if (current.?.parentNode()) |parent| { + _ = try parent.removeChild(current.?, page); + } + current = next; + } + } + self.collapse(true); } @@ -401,6 +436,39 @@ pub fn cloneContents(self: *const Range, page: *Page) !*DocumentFragment { } } } + } else { + // Complex case: different containers + // Clone partial start container + if (self._proto._start_container.is(Node.CData)) |_| { + const text_data = self._proto._start_container.getData(); + if (self._proto._start_offset < text_data.len) { + // Clone from start_offset to end of text + const cloned_text = text_data[self._proto._start_offset..]; + const text_node = try page.createTextNode(cloned_text); + _ = try fragment.asNode().appendChild(text_node, page); + } + } + + // Clone nodes between start and end containers (siblings case) + if (self._proto._start_container.parentNode() == self._proto._end_container.parentNode()) { + var current = self._proto._start_container.nextSibling(); + while (current != null and current != self._proto._end_container) { + const cloned = try current.?.cloneNode(true, page); + _ = try fragment.asNode().appendChild(cloned, page); + current = current.?.nextSibling(); + } + } + + // Clone partial end container + if (self._proto._end_container.is(Node.CData)) |_| { + const text_data = self._proto._end_container.getData(); + if (self._proto._end_offset > 0 and self._proto._end_offset <= text_data.len) { + // Clone from start to end_offset + const cloned_text = text_data[0..self._proto._end_offset]; + const text_node = try page.createTextNode(cloned_text); + _ = try fragment.asNode().appendChild(text_node, page); + } + } } return fragment;