mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 06:23:45 +00:00
Track owning documents for nodes which aren't the default document
Track this in a lookup on the page, to avoid having to store a pointer for _every_ node, given that most nodes _are_ owned by the document. This helps us ensure nodes can be properly adopted.
This commit is contained in:
@@ -95,6 +95,7 @@ _element_datasets: Element.DatasetLookup = .{},
|
|||||||
_element_class_lists: Element.ClassListLookup = .{},
|
_element_class_lists: Element.ClassListLookup = .{},
|
||||||
_element_rel_lists: Element.RelListLookup = .{},
|
_element_rel_lists: Element.RelListLookup = .{},
|
||||||
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
_element_shadow_roots: Element.ShadowRootLookup = .{},
|
||||||
|
_node_owner_documents: Node.OwnerDocumentLookup = .{},
|
||||||
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
_element_assigned_slots: Element.AssignedSlotLookup = .{},
|
||||||
|
|
||||||
_script_manager: ScriptManager,
|
_script_manager: ScriptManager,
|
||||||
@@ -266,6 +267,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
|
|||||||
self._element_class_lists = .{};
|
self._element_class_lists = .{};
|
||||||
self._element_rel_lists = .{};
|
self._element_rel_lists = .{};
|
||||||
self._element_shadow_roots = .{};
|
self._element_shadow_roots = .{};
|
||||||
|
self._node_owner_documents = .{};
|
||||||
self._element_assigned_slots = .{};
|
self._element_assigned_slots = .{};
|
||||||
self._notified_network_idle = .init;
|
self._notified_network_idle = .init;
|
||||||
self._notified_network_almost_idle = .init;
|
self._notified_network_almost_idle = .init;
|
||||||
@@ -1289,6 +1291,26 @@ pub fn nodeComplete(self: *Page, node: *Node) !void {
|
|||||||
return self.nodeIsReady(true, node);
|
return self.nodeIsReady(true, node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sets the owner document for a node. Only stores entries for nodes whose owner
|
||||||
|
// is NOT page.document to minimize memory overhead.
|
||||||
|
pub fn setNodeOwnerDocument(self: *Page, node: *Node, owner: *Document) !void {
|
||||||
|
if (owner == self.document) {
|
||||||
|
// No need to store if it's the main document - remove if present
|
||||||
|
_ = self._node_owner_documents.remove(node);
|
||||||
|
} else {
|
||||||
|
try self._node_owner_documents.put(self.arena, node, owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively sets the owner document for a node and all its descendants
|
||||||
|
pub fn adoptNodeTree(self: *Page, node: *Node, new_owner: *Document) !void {
|
||||||
|
try self.setNodeOwnerDocument(node, new_owner);
|
||||||
|
var it = node.childrenIterator();
|
||||||
|
while (it.next()) |child| {
|
||||||
|
try self.adoptNodeTree(child, new_owner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_iterator: anytype) !*Node {
|
pub fn createElement(self: *Page, ns_: ?[]const u8, name: []const u8, attribute_iterator: anytype) !*Node {
|
||||||
const namespace: Element.Namespace = blk: {
|
const namespace: Element.Namespace = blk: {
|
||||||
const ns = ns_ orelse break :blk .html;
|
const ns = ns_ orelse break :blk .html;
|
||||||
|
|||||||
16
src/browser/tests/node/adoption.html
Normal file
16
src/browser/tests/node/adoption.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="../testing.js"></script>
|
||||||
|
<body></body>
|
||||||
|
<script id="adoptNode">
|
||||||
|
const old = document.implementation.createHTMLDocument("");
|
||||||
|
const div = old.createElement("div");
|
||||||
|
div.appendChild(old.createTextNode("text"));
|
||||||
|
|
||||||
|
testing.expectEqual(old, div.ownerDocument);
|
||||||
|
testing.expectEqual(old, div.firstChild.ownerDocument);
|
||||||
|
|
||||||
|
document.body.appendChild(div);
|
||||||
|
|
||||||
|
testing.expectEqual(document, div.ownerDocument);
|
||||||
|
testing.expectEqual(document, div.firstChild.ownerDocument);
|
||||||
|
</script>
|
||||||
@@ -121,10 +121,15 @@ const CreateElementOptions = struct {
|
|||||||
is: ?[]const u8 = null,
|
is: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn createElement(_: *const Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElementOptions, page: *Page) !*Element {
|
||||||
const node = try page.createElement(null, name, null);
|
const node = try page.createElement(null, name, null);
|
||||||
const element = node.as(Element);
|
const element = node.as(Element);
|
||||||
|
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(node, self);
|
||||||
|
}
|
||||||
|
|
||||||
const options = options_ orelse return element;
|
const options = options_ orelse return element;
|
||||||
if (options.is) |is_value| {
|
if (options.is) |is_value| {
|
||||||
try element.setAttribute("is", is_value, page);
|
try element.setAttribute("is", is_value, page);
|
||||||
@@ -134,8 +139,13 @@ pub fn createElement(_: *const Document, name: []const u8, options_: ?CreateElem
|
|||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createElementNS(_: *const Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element {
|
pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8, page: *Page) !*Element {
|
||||||
const node = try page.createElement(namespace, name, null);
|
const node = try page.createElement(namespace, name, null);
|
||||||
|
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(node, self);
|
||||||
|
}
|
||||||
return node.as(Element);
|
return node.as(Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,28 +264,53 @@ pub fn getImplementation(_: *const Document) DOMImplementation {
|
|||||||
return .{};
|
return .{};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createDocumentFragment(_: *const Document, page: *Page) !*Node.DocumentFragment {
|
pub fn createDocumentFragment(self: *Document, page: *Page) !*Node.DocumentFragment {
|
||||||
return Node.DocumentFragment.init(page);
|
const frag = try Node.DocumentFragment.init(page);
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(frag.asNode(), self);
|
||||||
|
}
|
||||||
|
return frag;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createComment(_: *const Document, data: []const u8, page: *Page) !*Node {
|
pub fn createComment(self: *Document, data: []const u8, page: *Page) !*Node {
|
||||||
return page.createComment(data);
|
const node = try page.createComment(data);
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(node, self);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createTextNode(_: *const Document, data: []const u8, page: *Page) !*Node {
|
pub fn createTextNode(self: *Document, data: []const u8, page: *Page) !*Node {
|
||||||
return page.createTextNode(data);
|
const node = try page.createTextNode(data);
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(node, self);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createCDATASection(self: *const Document, data: []const u8, page: *Page) !*Node {
|
pub fn createCDATASection(self: *Document, data: []const u8, page: *Page) !*Node {
|
||||||
switch (self._type) {
|
const node = switch (self._type) {
|
||||||
.html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
.html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
||||||
.xml => return page.createCDATASection(data),
|
.xml => try page.createCDATASection(data),
|
||||||
.generic => return page.createCDATASection(data),
|
.generic => try page.createCDATASection(data),
|
||||||
|
};
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(node, self);
|
||||||
}
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn createProcessingInstruction(_: *const Document, target: []const u8, data: []const u8, page: *Page) !*Node {
|
pub fn createProcessingInstruction(self: *Document, target: []const u8, data: []const u8, page: *Page) !*Node {
|
||||||
return page.createProcessingInstruction(target, data);
|
const node = try page.createProcessingInstruction(target, data);
|
||||||
|
// Track owner document if it's not the main document
|
||||||
|
if (self != page.document) {
|
||||||
|
try page.setNodeOwnerDocument(node, self);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Range = @import("Range.zig");
|
const Range = @import("Range.zig");
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ _parent: ?*Node = null,
|
|||||||
_children: ?*Children = null,
|
_children: ?*Children = null,
|
||||||
_child_link: LinkedList.Node = .{},
|
_child_link: LinkedList.Node = .{},
|
||||||
|
|
||||||
|
// Lookup for nodes that have a different owner document than page.document
|
||||||
|
pub const OwnerDocumentLookup = std.AutoHashMapUnmanaged(*Node, *Document);
|
||||||
|
|
||||||
pub const Type = union(enum) {
|
pub const Type = union(enum) {
|
||||||
cdata: *CData,
|
cdata: *CData,
|
||||||
element: *Element,
|
element: *Element,
|
||||||
@@ -205,7 +208,6 @@ fn validateNodeInsertion(parent: *Node, node: *Node) !void {
|
|||||||
if (node._type == .attribute) {
|
if (node._type == .attribute) {
|
||||||
return error.HierarchyError;
|
return error.HierarchyError;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
||||||
@@ -222,10 +224,11 @@ pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
|||||||
// then we can remove + add a bit more efficiently (we don't have to fully
|
// then we can remove + add a bit more efficiently (we don't have to fully
|
||||||
// disconnect then reconnect)
|
// disconnect then reconnect)
|
||||||
const child_connected = child.isConnected();
|
const child_connected = child.isConnected();
|
||||||
|
|
||||||
// Check if we're adopting the node to a different document
|
// Check if we're adopting the node to a different document
|
||||||
const child_root = child.getRootNode(null);
|
const child_owner = child.ownerDocument(page);
|
||||||
const parent_root = self.getRootNode(null);
|
const parent_owner = self.ownerDocument(page) orelse self.as(Document);
|
||||||
const adopting_to_new_document = child_connected and child_root != parent_root;
|
const adopting_to_new_document = child_owner != null and child_owner.? != parent_owner;
|
||||||
|
|
||||||
if (child._parent) |parent| {
|
if (child._parent) |parent| {
|
||||||
// we can signal removeNode that the child will remain connected
|
// we can signal removeNode that the child will remain connected
|
||||||
@@ -233,6 +236,11 @@ pub fn appendChild(self: *Node, child: *Node, page: *Page) !*Node {
|
|||||||
page.removeNode(parent, child, .{ .will_be_reconnected = self.isConnected() });
|
page.removeNode(parent, child, .{ .will_be_reconnected = self.isConnected() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adopt the node tree if moving between documents
|
||||||
|
if (adopting_to_new_document) {
|
||||||
|
try page.adoptNodeTree(child, parent_owner);
|
||||||
|
}
|
||||||
|
|
||||||
try page.appendNode(self, child, .{
|
try page.appendNode(self, child, .{
|
||||||
.child_already_connected = child_connected,
|
.child_already_connected = child_connected,
|
||||||
.adopting_to_new_document = adopting_to_new_document,
|
.adopting_to_new_document = adopting_to_new_document,
|
||||||
@@ -432,8 +440,13 @@ pub fn ownerDocument(self: *const Node, page: *const Page) ?*Document {
|
|||||||
return current._type.document;
|
return current._type.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, this is a detached node. The owner is the document that
|
// Otherwise, this is a detached node. Check if it has a specific owner
|
||||||
// created it. For now, we only have one document.
|
// document registered (for nodes created via non-main documents).
|
||||||
|
if (page._node_owner_documents.get(@constCast(self))) |owner| {
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to the main document for detached nodes without a specific owner.
|
||||||
return page.document;
|
return page.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,10 +502,11 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
|||||||
try validateNodeInsertion(self, new_node);
|
try validateNodeInsertion(self, new_node);
|
||||||
|
|
||||||
const child_already_connected = new_node.isConnected();
|
const child_already_connected = new_node.isConnected();
|
||||||
|
|
||||||
// Check if we're adopting the node to a different document
|
// Check if we're adopting the node to a different document
|
||||||
const child_root = new_node.getRootNode(null);
|
const child_owner = new_node.ownerDocument(page);
|
||||||
const parent_root = self.getRootNode(null);
|
const parent_owner = self.ownerDocument(page) orelse self.as(Document);
|
||||||
const adopting_to_new_document = child_already_connected and child_root != parent_root;
|
const adopting_to_new_document = child_owner != null and child_owner.? != parent_owner;
|
||||||
|
|
||||||
page.domChanged();
|
page.domChanged();
|
||||||
const will_be_reconnected = self.isConnected();
|
const will_be_reconnected = self.isConnected();
|
||||||
@@ -500,6 +514,11 @@ pub fn insertBefore(self: *Node, new_node: *Node, ref_node_: ?*Node, page: *Page
|
|||||||
page.removeNode(parent, new_node, .{ .will_be_reconnected = will_be_reconnected });
|
page.removeNode(parent, new_node, .{ .will_be_reconnected = will_be_reconnected });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adopt the node tree if moving between documents
|
||||||
|
if (adopting_to_new_document) {
|
||||||
|
try page.adoptNodeTree(new_node, parent_owner);
|
||||||
|
}
|
||||||
|
|
||||||
try page.insertNodeRelative(
|
try page.insertNodeRelative(
|
||||||
self,
|
self,
|
||||||
new_node,
|
new_node,
|
||||||
|
|||||||
Reference in New Issue
Block a user