mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
Merge pull request #1352 from lightpanda-io/mutation_character_data
Mutation character data
This commit is contained in:
@@ -820,3 +820,137 @@
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=deleteContents_crossNode>
|
||||
{
|
||||
// Test deleteContents across multiple sibling text nodes
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(document.createTextNode('AAAA'));
|
||||
p.appendChild(document.createTextNode('BBBB'));
|
||||
p.appendChild(document.createTextNode('CCCC'));
|
||||
|
||||
testing.expectEqual(3, p.childNodes.length);
|
||||
testing.expectEqual('AAAABBBBCCCC', p.textContent);
|
||||
|
||||
const range = document.createRange();
|
||||
// Start at position 2 in first text node ("AA|AA")
|
||||
range.setStart(p.childNodes[0], 2);
|
||||
// End at position 2 in third text node ("CC|CC")
|
||||
range.setEnd(p.childNodes[2], 2);
|
||||
|
||||
range.deleteContents();
|
||||
|
||||
// Should have truncated first node to "AA" and third node to "CC"
|
||||
// Middle node should be removed
|
||||
testing.expectEqual(2, p.childNodes.length);
|
||||
testing.expectEqual('AA', p.childNodes[0].textContent);
|
||||
testing.expectEqual('CC', p.childNodes[1].textContent);
|
||||
testing.expectEqual('AACC', p.textContent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=deleteContents_crossNode_partial>
|
||||
{
|
||||
// Test deleteContents where start node is completely preserved
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(document.createTextNode('KEEP'));
|
||||
p.appendChild(document.createTextNode('DELETE'));
|
||||
p.appendChild(document.createTextNode('PARTIAL'));
|
||||
|
||||
const range = document.createRange();
|
||||
// Start at end of first text node
|
||||
range.setStart(p.childNodes[0], 4);
|
||||
// End in middle of third text node
|
||||
range.setEnd(p.childNodes[2], 4);
|
||||
|
||||
range.deleteContents();
|
||||
|
||||
testing.expectEqual(2, p.childNodes.length);
|
||||
testing.expectEqual('KEEP', p.childNodes[0].textContent);
|
||||
testing.expectEqual('IAL', p.childNodes[1].textContent);
|
||||
testing.expectEqual('KEEPIAL', p.textContent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=extractContents_crossNode>
|
||||
{
|
||||
// Test extractContents across multiple sibling text nodes
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(document.createTextNode('AAAA'));
|
||||
p.appendChild(document.createTextNode('BBBB'));
|
||||
p.appendChild(document.createTextNode('CCCC'));
|
||||
|
||||
const range = document.createRange();
|
||||
range.setStart(p.childNodes[0], 2);
|
||||
range.setEnd(p.childNodes[2], 2);
|
||||
|
||||
const fragment = range.extractContents();
|
||||
|
||||
// Original should be truncated
|
||||
testing.expectEqual(2, p.childNodes.length);
|
||||
testing.expectEqual('AA', p.childNodes[0].textContent);
|
||||
testing.expectEqual('CC', p.childNodes[1].textContent);
|
||||
|
||||
// Fragment should contain extracted content
|
||||
testing.expectEqual(3, fragment.childNodes.length);
|
||||
testing.expectEqual('AA', fragment.childNodes[0].textContent);
|
||||
testing.expectEqual('BBBB', fragment.childNodes[1].textContent);
|
||||
testing.expectEqual('CC', fragment.childNodes[2].textContent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=cloneContents_crossNode>
|
||||
{
|
||||
// Test cloneContents across multiple sibling text nodes
|
||||
const p = document.createElement('p');
|
||||
p.appendChild(document.createTextNode('AAAA'));
|
||||
p.appendChild(document.createTextNode('BBBB'));
|
||||
p.appendChild(document.createTextNode('CCCC'));
|
||||
|
||||
const range = document.createRange();
|
||||
range.setStart(p.childNodes[0], 2);
|
||||
range.setEnd(p.childNodes[2], 2);
|
||||
|
||||
const fragment = range.cloneContents();
|
||||
|
||||
// Original should be unchanged
|
||||
testing.expectEqual(3, p.childNodes.length);
|
||||
testing.expectEqual('AAAA', p.childNodes[0].textContent);
|
||||
testing.expectEqual('BBBB', p.childNodes[1].textContent);
|
||||
testing.expectEqual('CCCC', p.childNodes[2].textContent);
|
||||
|
||||
// Fragment should contain cloned content
|
||||
testing.expectEqual(3, fragment.childNodes.length);
|
||||
testing.expectEqual('AA', fragment.childNodes[0].textContent);
|
||||
testing.expectEqual('BBBB', fragment.childNodes[1].textContent);
|
||||
testing.expectEqual('CC', fragment.childNodes[2].textContent);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=deleteContents_crossNode_withElements>
|
||||
{
|
||||
// Test deleteContents with mixed text and element nodes
|
||||
const div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode('Start'));
|
||||
const span = document.createElement('span');
|
||||
span.textContent = 'Middle';
|
||||
div.appendChild(span);
|
||||
div.appendChild(document.createTextNode('End'));
|
||||
|
||||
testing.expectEqual(3, div.childNodes.length);
|
||||
|
||||
const range = document.createRange();
|
||||
// Start in middle of first text node
|
||||
range.setStart(div.childNodes[0], 2);
|
||||
// End in middle of last text node
|
||||
range.setEnd(div.childNodes[2], 1);
|
||||
|
||||
range.deleteContents();
|
||||
|
||||
// Should keep "St" from start, remove span, keep "nd" from end
|
||||
testing.expectEqual(2, div.childNodes.length);
|
||||
testing.expectEqual('St', div.childNodes[0].textContent);
|
||||
testing.expectEqual('nd', div.childNodes[1].textContent);
|
||||
testing.expectEqual('Stnd', div.textContent);
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -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, .{});
|
||||
|
||||
@@ -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 => {},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user