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;