mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-06 11:21:36 -04:00
Merge pull request #3510 from lightpanda-io/node-document
internal: store document in every node
This commit is contained in:
33 files changed
+661
-500
No files matched your search
@@ -65,16 +65,17 @@ pub fn reset(self: *NodeRegistry) void {
|
||||
}
|
||||
|
||||
/// Evict only the nodes owned by `frame`'s page, leaving sibling pages' node
|
||||
/// IDs valid. Must run before the page's arena is freed — attribution walks
|
||||
/// each node's live parent chain.
|
||||
/// IDs valid. Must run before the page's arena is freed — attribution reads
|
||||
/// each node's document.
|
||||
pub fn resetFrame(self: *NodeRegistry, arena: Allocator, frame: *Frame) void {
|
||||
const page = frame._page;
|
||||
var doomed: std.ArrayListUnmanaged(*Node) = .empty;
|
||||
var it = self.lookup_by_id.valueIterator();
|
||||
while (it.next()) |node_ptr| {
|
||||
const node = node_ptr.*;
|
||||
const owner = node.dom.ownerFrame(frame) orelse frame;
|
||||
if (owner._page == page) {
|
||||
// The document table is the browser's, so a sibling page's node
|
||||
// resolves correctly through this page's frame.
|
||||
if (node.dom.getDocument(frame)._page == page) {
|
||||
doomed.append(arena, node) catch return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ const js = @import("js/js.zig");
|
||||
const Page = @import("Page.zig");
|
||||
const Session = @import("Session.zig");
|
||||
const Viewport = @import("Viewport.zig");
|
||||
const DocumentRegistry = @import("DocumentRegistry.zig");
|
||||
|
||||
const Selector = @import("webapi/selector/Selector.zig");
|
||||
const Geolocation = @import("webapi/geolocation/Geolocation.zig");
|
||||
const PermissionState = @import("webapi/Permissions.zig").State;
|
||||
@@ -73,6 +75,11 @@ renderer: ?*lp.screenshot.Renderer = null,
|
||||
// Runtime geolocation override
|
||||
geolocation_override: ?Geolocation.Override = null,
|
||||
|
||||
// Every Document allocated in this browser session, allows nodes to refer to
|
||||
// documents by their index. (TODO: this will probably eventually be moved
|
||||
// to the Page, but we need other changes first)
|
||||
documents: DocumentRegistry,
|
||||
|
||||
// used by sessions to allocate pages.
|
||||
page_pool: std.heap.MemoryPool(Page),
|
||||
|
||||
@@ -123,6 +130,7 @@ pub fn init(self: *Browser, app: *App, opts: InitOpts) !void {
|
||||
.env = env,
|
||||
.session = null,
|
||||
.page_pool = .empty,
|
||||
.documents = .init(allocator),
|
||||
.allocator = allocator,
|
||||
.arena_pool = &app.arena_pool,
|
||||
.http_client = undefined,
|
||||
@@ -155,6 +163,7 @@ pub fn deinit(self: *Browser) void {
|
||||
// fire — only now is it safe to free the pool backing their parameters.
|
||||
self.fc_identity_pool.deinit(allocator);
|
||||
self.page_pool.deinit(allocator);
|
||||
self.documents.deinit();
|
||||
self.http_client.deinit();
|
||||
if (self.renderer) |r| r.deinit();
|
||||
self.clearPermissions();
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||
//
|
||||
// Francis Bouvier <francis@lightpanda.io>
|
||||
// Pierre Tachoire <pierre@lightpanda.io>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Every Document created by the Browser. Allows Node to reference them by
|
||||
// index (u32).
|
||||
|
||||
const std = @import("std");
|
||||
const lp = @import("lightpanda");
|
||||
|
||||
const Document = @import("webapi/Document.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const DocumentRegistry = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
items: std.ArrayList(*Document) = .empty,
|
||||
free: std.ArrayList(u32) = .empty,
|
||||
|
||||
pub fn init(allocator: Allocator) DocumentRegistry {
|
||||
return .{ .allocator = allocator };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *DocumentRegistry) void {
|
||||
self.items.deinit(self.allocator);
|
||||
self.free.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn get(self: *const DocumentRegistry, index: u32) *Document {
|
||||
return self.items.items[index];
|
||||
}
|
||||
|
||||
pub fn register(self: *DocumentRegistry, doc: *Document) !u32 {
|
||||
if (self.free.pop()) |index| {
|
||||
self.items.items[index] = doc;
|
||||
return index;
|
||||
}
|
||||
const index = std.math.cast(u32, self.items.items.len) orelse {
|
||||
log.warn(.browser, "document limit", .{ .count = self.items.items.len });
|
||||
return error.QuotaExceeded;
|
||||
};
|
||||
try self.items.append(self.allocator, doc);
|
||||
return index;
|
||||
}
|
||||
|
||||
pub fn release(self: *DocumentRegistry, index: u32) void {
|
||||
// The slot keeps its stale pointer; nothing resolves it until reused.
|
||||
// Failing to record it only loses the slot.
|
||||
self.free.append(self.allocator, index) catch {};
|
||||
}
|
||||
+97
-23
@@ -23,21 +23,24 @@ const reflect = @import("reflect.zig");
|
||||
|
||||
const SlabAllocator = @import("../slab.zig").SlabAllocator;
|
||||
|
||||
const Page = @import("Page.zig");
|
||||
const Frame = @import("Frame.zig");
|
||||
const DocumentRegistry = @import("DocumentRegistry.zig");
|
||||
|
||||
const Node = @import("webapi/Node.zig");
|
||||
const Blob = @import("webapi/Blob.zig");
|
||||
const Event = @import("webapi/Event.zig");
|
||||
const UIEvent = @import("webapi/event/UIEvent.zig");
|
||||
const MouseEvent = @import("webapi/event/MouseEvent.zig");
|
||||
const DOMRect = @import("webapi/DOMRect.zig");
|
||||
const Element = @import("webapi/Element.zig");
|
||||
const Document = @import("webapi/Document.zig");
|
||||
const UIEvent = @import("webapi/event/UIEvent.zig");
|
||||
const EventTarget = @import("webapi/EventTarget.zig");
|
||||
const AbortSignal = @import("webapi/AbortSignal.zig");
|
||||
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
|
||||
const IDBRequest = @import("webapi/storage/idb/IDBRequest.zig");
|
||||
const Blob = @import("webapi/Blob.zig");
|
||||
const MouseEvent = @import("webapi/event/MouseEvent.zig");
|
||||
const AbstractRange = @import("webapi/AbstractRange.zig");
|
||||
const DOMRect = @import("webapi/DOMRect.zig");
|
||||
const DOMRectReadOnly = @import("webapi/DOMRectReadOnly.zig");
|
||||
const IDBRequest = @import("webapi/storage/idb/IDBRequest.zig");
|
||||
const XMLHttpRequestEventTarget = @import("webapi/net/XMLHttpRequestEventTarget.zig");
|
||||
|
||||
const log = lp.log;
|
||||
const String = lp.String;
|
||||
@@ -47,20 +50,37 @@ const Allocator = std.mem.Allocator;
|
||||
// Shared across all frames of a Page.
|
||||
const Factory = @This();
|
||||
|
||||
_page: *Page,
|
||||
_arena: Allocator,
|
||||
_slab: SlabAllocator,
|
||||
_documents: std.ArrayList(u32) = .empty, // ids of the documents _we_ created
|
||||
_document_registry: *DocumentRegistry, // &browser.documents
|
||||
|
||||
pub fn init(arena: Allocator) Factory {
|
||||
pub fn init(page: *Page, arena: Allocator, document_registry: *DocumentRegistry) Factory {
|
||||
return .{
|
||||
._page = page,
|
||||
._arena = arena,
|
||||
._slab = SlabAllocator.init(arena, 128),
|
||||
._document_registry = document_registry,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Factory) void {
|
||||
for (self._documents.items) |index| {
|
||||
self._document_registry.release(index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn storageAllocator(self: *Factory) Allocator {
|
||||
return self._slab.allocator();
|
||||
}
|
||||
|
||||
fn registerDocument(self: *Factory, doc: *Document) !u32 {
|
||||
const index = try self._document_registry.register(doc);
|
||||
try self._documents.append(self._arena, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
// this is a root object
|
||||
pub fn eventTarget(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
return self.eventTargetWithAllocator(self._slab.allocator(), child);
|
||||
@@ -224,6 +244,22 @@ fn AutoPrototypeChain(comptime types: []const type) type {
|
||||
chain.setLeaf(types.len - 1, leaf_value);
|
||||
return chain.get(types.len - 1);
|
||||
}
|
||||
|
||||
// Same, for a node chain: stamps the Node with its document.
|
||||
fn createOwned(allocator: std.mem.Allocator, owner: u32, leaf_value: anytype) !*@TypeOf(leaf_value) {
|
||||
comptime assert(types[1] == Node);
|
||||
const chain = try PrototypeChain(types).allocate(allocator);
|
||||
|
||||
chain.setRoot();
|
||||
|
||||
inline for (1..types.len - 1) |i| {
|
||||
chain.setMiddle(i);
|
||||
}
|
||||
|
||||
chain.setLeaf(types.len - 1, leaf_value);
|
||||
chain.get(1)._owner = owner;
|
||||
return chain.get(types.len - 1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -303,24 +339,62 @@ pub fn domRect(self: *Factory, rect: DOMRectReadOnly.Data) !*DOMRect {
|
||||
return chain.get(1);
|
||||
}
|
||||
|
||||
pub fn node(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
pub fn node(self: *Factory, owner: *const Document, child: anytype) !*@TypeOf(child) {
|
||||
comptime assert(@TypeOf(child) != Document);
|
||||
const allocator = self._slab.allocator();
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, Node, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
).createOwned(allocator, owner._index, child);
|
||||
}
|
||||
|
||||
pub const DocumentOpts = struct {
|
||||
url: ?[:0]const u8 = null,
|
||||
charset: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
// A Document with no more specific type (`new Document()`, XHR's responseXML).
|
||||
pub fn genericDocument(self: *Factory, opts: DocumentOpts) !*Document {
|
||||
const chain = try self.documentChain(&.{ EventTarget, Node, Document }, opts);
|
||||
return chain.get(2);
|
||||
}
|
||||
|
||||
// Documents: {EventTarget, Node, Document, [Leaf]}. The Document is registered
|
||||
// in the browser's table as it is built, so it knows its page and slot from
|
||||
// the start.
|
||||
fn documentChain(self: *Factory, comptime types: []const type, opts: DocumentOpts) !PrototypeChain(types) {
|
||||
comptime assert(types[1] == Node and types[2] == Document);
|
||||
const chain = try PrototypeChain(types).allocate(self._slab.allocator());
|
||||
const doc = chain.get(2);
|
||||
const index = try self.registerDocument(doc);
|
||||
|
||||
chain.setRoot();
|
||||
chain.setMiddle(1);
|
||||
chain.get(1)._owner = index;
|
||||
|
||||
doc.* = .{
|
||||
._proto = undefined,
|
||||
._type = if (comptime types.len == 3) .generic else typeInit(Document, chain.get(3)),
|
||||
._page = self._page,
|
||||
._index = index,
|
||||
._url = opts.url,
|
||||
._charset = opts.charset,
|
||||
};
|
||||
setProto(doc, chain.get(1));
|
||||
return chain;
|
||||
}
|
||||
|
||||
// CData nodes: {EventTarget, Node, CData, [Text,] Leaf}.
|
||||
// CData is special, it's _type is a bare tag, not a tagged union. A website can
|
||||
// have tens of thousands of Text nodes, and this allows a few optimization to
|
||||
// both reduce the # of allocations and the size
|
||||
pub fn cdataNode(self: *Factory, cd: Node.CData, leaf: anytype) !*Node.CData {
|
||||
pub fn cdataNode(self: *Factory, owner: *const Document, cd: Node.CData, leaf: anytype) !*Node.CData {
|
||||
const types = comptime prototypeTypes(@TypeOf(leaf));
|
||||
comptime assert(types[0] == EventTarget and types[1] == Node and types[2] == Node.CData);
|
||||
|
||||
const chain = try PrototypeChain(types).allocate(self._slab.allocator());
|
||||
chain.setRoot();
|
||||
chain.setMiddle(1);
|
||||
chain.get(1)._owner = owner._index;
|
||||
|
||||
const cd_ptr = chain.get(2);
|
||||
cd_ptr.* = cd;
|
||||
@@ -436,41 +510,40 @@ fn ChainedLeaf(comptime Values: type) type {
|
||||
}
|
||||
|
||||
pub fn document(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, Node, Document, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
const chain = try self.documentChain(&.{ EventTarget, Node, Document, @TypeOf(child) }, .{});
|
||||
chain.setLeaf(3, child);
|
||||
return chain.get(3);
|
||||
}
|
||||
|
||||
pub fn documentFragment(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
pub fn documentFragment(self: *Factory, owner: *const Document, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, Node, Node.DocumentFragment, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
).createOwned(allocator, owner._index, child);
|
||||
}
|
||||
|
||||
pub fn element(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
pub fn element(self: *Factory, owner: *const Document, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, Node, Element, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
).createOwned(allocator, owner._index, child);
|
||||
}
|
||||
|
||||
pub fn htmlElement(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
pub fn htmlElement(self: *Factory, owner: *const Document, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, Node, Element, Element.Html, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
).createOwned(allocator, owner._index, child);
|
||||
}
|
||||
|
||||
pub fn htmlMediaElement(self: *Factory, child: anytype) !*@TypeOf(child) {
|
||||
pub fn htmlMediaElement(self: *Factory, owner: *const Document, child: anytype) !*@TypeOf(child) {
|
||||
const allocator = self._slab.allocator();
|
||||
return try AutoPrototypeChain(
|
||||
&.{ EventTarget, Node, Element, Element.Html, Element.Html.Media, @TypeOf(child) },
|
||||
).create(allocator, child);
|
||||
).createOwned(allocator, owner._index, child);
|
||||
}
|
||||
|
||||
pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
|
||||
pub fn svgElement(self: *Factory, owner: *const Document, tag_name: []const u8, child: anytype) !*@TypeOf(child) {
|
||||
const types = comptime svgPrototypeTypes(@TypeOf(child));
|
||||
const chain = try PrototypeChain(types).allocate(self._slab.allocator());
|
||||
|
||||
@@ -489,6 +562,7 @@ pub fn svgElement(self: *Factory, tag_name: []const u8, child: anytype) !*@TypeO
|
||||
}
|
||||
}
|
||||
chain.setLeaf(types.len - 1, child);
|
||||
chain.get(1)._owner = owner._index;
|
||||
return chain.get(types.len - 1);
|
||||
}
|
||||
|
||||
|
||||
+2
-16
@@ -151,7 +151,6 @@ _element_rel_lists: Element.RelListLookup = .empty,
|
||||
_element_part_lists: Element.PartListLookup = .empty,
|
||||
_element_token_lists: Element.TokenListLookup = .empty,
|
||||
_element_shadow_roots: Element.ShadowRootLookup = .empty,
|
||||
_node_owner_documents: Node.OwnerDocumentLookup = .empty,
|
||||
_element_scroll_positions: Element.ScrollPositionLookup = .empty,
|
||||
_element_namespace_uris: Element.NamespaceUriLookup = .empty,
|
||||
_svg_animated_enumerations: AnimatedEnumeration.Lookup = .empty,
|
||||
@@ -255,9 +254,6 @@ _custom_element_creation: enum {
|
||||
// constructor must not run (you end up in an endless loop if the constructor
|
||||
// does this.innerHTML = '...', which happens).
|
||||
bare_context,
|
||||
// The target document has no custom element registry (e.g. DOMParser). The
|
||||
// element stays undefined until it's inserted into the frame's document.
|
||||
undefined,
|
||||
} = .construct,
|
||||
|
||||
// List of custom elements that were created before their definition was registered
|
||||
@@ -484,6 +480,7 @@ pub fn init(self: *Frame, frame_id: u32, page: *Page, opts: InitOpts) !void {
|
||||
location.acquireRef();
|
||||
self.window._location = location;
|
||||
|
||||
lp.assert(document._page == page, "unexpected document page", .{});
|
||||
document._frame = self;
|
||||
|
||||
if (comptime lp.IS_TEST == false) {
|
||||
@@ -2586,20 +2583,9 @@ pub fn nodeComplete(self: *Frame, node: *Node) !void {
|
||||
return self.nodeIsReady(true, node);
|
||||
}
|
||||
|
||||
// Sets the owner document for a node. Only stores entries for nodes whose owner
|
||||
// is NOT frame.document to minimize memory overhead.
|
||||
pub fn setNodeOwnerDocument(self: *Frame, 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: *Frame, node: *Node, old_owner: *Document, new_owner: *Document) !void {
|
||||
try self.setNodeOwnerDocument(node, new_owner);
|
||||
node._owner = new_owner._index;
|
||||
|
||||
// Per spec, adopted steps run on each element after its document is set.
|
||||
if (node.is(Element)) |el| {
|
||||
|
||||
@@ -181,7 +181,7 @@ pub fn init(self: *Page, session: *Session, frame_id: u32) !void {
|
||||
.frame = undefined,
|
||||
._frame_arena = frame_arena,
|
||||
.frame_arena = frame_arena.allocator(),
|
||||
.factory = Factory.init(frame_arena.allocator()),
|
||||
.factory = Factory.init(self, frame_arena.allocator(), &session.browser.documents),
|
||||
.globals = .init(session.browser.app.allocator),
|
||||
};
|
||||
self.queued_navigation = &self.queued_navigation_1;
|
||||
@@ -254,6 +254,7 @@ pub fn deinit(self: *Page) void {
|
||||
self.origins = .empty;
|
||||
}
|
||||
|
||||
self.factory.deinit();
|
||||
self._frame_arena.release();
|
||||
}
|
||||
|
||||
|
||||
+211
-187
File diff suppressed because it is too large.
Load diff
@@ -46,18 +46,6 @@ pub fn fragment(frame: *Frame, node: *Node, html: []const u8, opts: FragmentPars
|
||||
frame._parse_mode = .fragment;
|
||||
defer frame._parse_mode = previous_parse_mode;
|
||||
|
||||
// A context element in a document without a browsing context
|
||||
// (createHTMLDocument, new Document, DOMParser output) has no custom
|
||||
// element registry.
|
||||
const previous_creation = frame._custom_element_creation;
|
||||
const document = node.ownerDocument(frame) orelse node.as(Document);
|
||||
if (document._frame == null) {
|
||||
// a document without a browsing context (DOMParser et al.) has
|
||||
// no custom element and should stay undefined.
|
||||
frame._custom_element_creation = .undefined;
|
||||
}
|
||||
defer frame._custom_element_creation = previous_creation;
|
||||
|
||||
// The html5ever wrapper-unwrap below rebinds children without going
|
||||
// through the insertion path, so recompute slot assignments for any
|
||||
// shadow tree this fragment landed in (idempotent; signals only on diff).
|
||||
@@ -122,11 +110,6 @@ pub fn xmlDocument(frame: *Frame, xml: []const u8) !?*Document.XMLDocument {
|
||||
frame._parse_mode = .fragment;
|
||||
defer frame._parse_mode = previous_parse_mode;
|
||||
|
||||
// No browsing context, so no custom element registry.
|
||||
const previous_creation = frame._custom_element_creation;
|
||||
frame._custom_element_creation = .undefined;
|
||||
defer frame._custom_element_creation = previous_creation;
|
||||
|
||||
const doc = try frame._factory.document(Document.XMLDocument{ ._proto = undefined });
|
||||
const doc_node = doc.asNode();
|
||||
var parser = Parser.init(arena.allocator(), doc_node, frame, .{});
|
||||
|
||||
@@ -60,8 +60,9 @@ const Parser = @This();
|
||||
|
||||
frame: *Frame,
|
||||
err: ?Error,
|
||||
container: ParsedNode,
|
||||
arena: Allocator,
|
||||
container: ParsedNode,
|
||||
document: *Node.Document,
|
||||
strings: std.StringHashMapUnmanaged(void),
|
||||
pending_text: ?PendingText,
|
||||
// One buffer reused across every text run in this parser. clearRetainingCapacity
|
||||
@@ -100,6 +101,7 @@ pub fn init(arena: Allocator, node: *Node, frame: *Frame, opts: Options) Parser
|
||||
return .{
|
||||
.err = null,
|
||||
.frame = frame,
|
||||
.document = node.getDocument(frame),
|
||||
.strings = .empty,
|
||||
.arena = arena,
|
||||
.container = ParsedNode{
|
||||
@@ -159,7 +161,7 @@ fn appendTextChunk(self: *Parser, parent: *Node, txt: []const u8) !void {
|
||||
|
||||
// Fresh text run: the first chunk lives on _data only. buf stays empty
|
||||
// until (and unless) a second chunk arrives.
|
||||
const new_text = try Frame.node_factory.createTextNode(self.frame, txt);
|
||||
const new_text = try Frame.node_factory.createTextNode(self.document, txt);
|
||||
try self.frame.appendNew(parent, new_text);
|
||||
self.inserted_since_checkpoint +|= 1;
|
||||
self.pending_text = .{
|
||||
@@ -498,9 +500,10 @@ fn createXMLElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualNa
|
||||
// create.
|
||||
fn createContextElementCallback(ctx: *anyopaque, data: *anyopaque, qname: h5e.QualName, attributes: h5e.AttributeIterator) callconv(.c) ?*anyopaque {
|
||||
const self: *Parser = @ptrCast(@alignCast(ctx));
|
||||
const previous_creation = self.frame._custom_element_creation;
|
||||
self.frame._custom_element_creation = .bare_context;
|
||||
defer self.frame._custom_element_creation = previous_creation;
|
||||
const frame = self.document._frame orelse self.frame;
|
||||
const previous_creation = frame._custom_element_creation;
|
||||
frame._custom_element_creation = .bare_context;
|
||||
defer frame._custom_element_creation = previous_creation;
|
||||
return self._createElementCallback(data, qname, attributes, .unknown) catch |err| {
|
||||
self.err = .{ .err = err, .source = .create_element };
|
||||
return null;
|
||||
@@ -527,7 +530,7 @@ fn _createElementCallback(self: *Parser, data: *anyopaque, qname: h5e.QualName,
|
||||
} else local;
|
||||
const namespace_string = qname.ns.slice();
|
||||
const namespace = if (namespace_string.len == 0) default_namespace else Element.Namespace.parse(namespace_string);
|
||||
const node = try Frame.node_factory.createElementNS(frame, namespace, name, attributes);
|
||||
const node = try Frame.node_factory.createElementNS(self.document, namespace, name, attributes);
|
||||
if (namespace == .unknown and namespace_string.len > 0) {
|
||||
// Same as Document.createElementNS: keep the URI so namespaceURI and
|
||||
// lookupNamespaceURI can return it.
|
||||
@@ -552,8 +555,7 @@ fn createCommentCallback(ctx: *anyopaque, str: h5e.StringSlice) callconv(.c) ?*a
|
||||
};
|
||||
}
|
||||
fn _createCommentCallback(self: *Parser, str: []const u8) !*anyopaque {
|
||||
const frame = self.frame;
|
||||
const node = try Frame.node_factory.createComment(frame, str);
|
||||
const node = try Frame.node_factory.createComment(self.document, str);
|
||||
const pn = try self.arena.create(ParsedNode);
|
||||
pn.* = .{
|
||||
.data = null,
|
||||
@@ -572,8 +574,7 @@ fn createProcessingInstruction(ctx: *anyopaque, target: h5e.StringSlice, data: h
|
||||
};
|
||||
}
|
||||
fn _createProcessingInstruction(self: *Parser, target: []const u8, data: []const u8) !*anyopaque {
|
||||
const frame = self.frame;
|
||||
const node = try Frame.node_factory.createProcessingInstruction(frame, target, data);
|
||||
const node = try Frame.node_factory.createProcessingInstruction(self.document, target, data);
|
||||
const pn = try self.arena.create(ParsedNode);
|
||||
pn.* = .{
|
||||
.data = null,
|
||||
@@ -598,7 +599,7 @@ fn _appendDoctypeToDocument(self: *Parser, name: []const u8, public_id: []const
|
||||
|
||||
// Create the DocumentType node
|
||||
const DocumentType = @import("../webapi/DocumentType.zig");
|
||||
const doctype = try frame._factory.node(DocumentType{
|
||||
const doctype = try frame._factory.node(self.document, DocumentType{
|
||||
._proto = undefined,
|
||||
._name = try frame.dupeString(name),
|
||||
._public_id = try frame.dupeString(public_id),
|
||||
@@ -808,7 +809,7 @@ fn _appendBeforeSiblingCallback(self: *Parser, sibling: *Node, node_or_text: h5e
|
||||
}
|
||||
break :blk child;
|
||||
},
|
||||
.text => |txt| try Frame.node_factory.createTextNode(self.frame, txt),
|
||||
.text => |txt| try Frame.node_factory.createTextNode(self.document, txt),
|
||||
.failed => return,
|
||||
};
|
||||
try self.frame.insertNodeRelative(parent, node, .{ .before = sibling }, .{});
|
||||
|
||||
@@ -551,6 +551,17 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=noBrowsingContext>
|
||||
{
|
||||
// The parsed document has no browsing context, so its <base> belongs to
|
||||
// it alone: the page's base URL is untouched.
|
||||
const base = document.baseURI;
|
||||
const doc = new DOMParser().parseFromString('<base href="http://other.example/x/">', 'text/html');
|
||||
testing.expectEqual('BASE', doc.querySelector('base').tagName);
|
||||
testing.expectEqual(base, document.baseURI);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=invalidType>
|
||||
{
|
||||
let err = null;
|
||||
|
||||
@@ -34,3 +34,63 @@
|
||||
testing.expectEqual(false, anotherDoc.contains(anotherNewNode));
|
||||
testing.expectEqual(false, newDoc.contains(newNode));
|
||||
</script>
|
||||
|
||||
<script id=detached_keeps_document>
|
||||
{
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString('<div><p>x</p></div>', 'text/html');
|
||||
const el = doc.createElement('span');
|
||||
testing.expectEqual(doc, el.ownerDocument);
|
||||
testing.expectEqual(doc, doc.createTextNode('x').ownerDocument);
|
||||
testing.expectEqual(doc, doc.createComment('x').ownerDocument);
|
||||
testing.expectEqual(doc, doc.createDocumentFragment().ownerDocument);
|
||||
testing.expectEqual(doc, doc.createAttribute('a').ownerDocument);
|
||||
testing.expectEqual(doc, el.cloneNode(true).ownerDocument);
|
||||
|
||||
// Removal does not change the node document
|
||||
const div = doc.querySelector('div');
|
||||
div.remove();
|
||||
testing.expectEqual(doc, div.ownerDocument);
|
||||
testing.expectEqual(doc, div.firstChild.ownerDocument);
|
||||
|
||||
// Insertion into another document adopts the whole subtree
|
||||
document.body.appendChild(div);
|
||||
testing.expectEqual(document, div.ownerDocument);
|
||||
testing.expectEqual(document, div.firstChild.ownerDocument);
|
||||
div.remove();
|
||||
testing.expectEqual(document, div.ownerDocument);
|
||||
|
||||
// Attributes follow their element
|
||||
div.setAttribute('a', '1');
|
||||
testing.expectEqual(document, div.getAttributeNode('a').ownerDocument);
|
||||
const attr = doc.createAttribute('b');
|
||||
div.setAttributeNode(attr);
|
||||
testing.expectEqual(document, attr.ownerDocument);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=iframe_document type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
const f = document.createElement('iframe');
|
||||
f.srcdoc = '<p>hello</p>';
|
||||
f.onload = () => state.resolve(f);
|
||||
document.body.appendChild(f);
|
||||
|
||||
await state.done((f) => {
|
||||
const doc = f.contentDocument;
|
||||
// Created from the parent's realm, owned by the iframe's document
|
||||
const el = doc.createElement('div');
|
||||
testing.expectEqual(doc, el.ownerDocument);
|
||||
testing.expectEqual(doc, doc.createTextNode('x').ownerDocument);
|
||||
testing.expectEqual(doc, doc.querySelector('p').cloneNode().ownerDocument);
|
||||
|
||||
testing.expectEqual(document, document.adoptNode(el).ownerDocument);
|
||||
testing.expectEqual(doc, doc.adoptNode(el).ownerDocument);
|
||||
const imported = document.importNode(doc.querySelector('p'), true);
|
||||
testing.expectEqual(document, imported.ownerDocument);
|
||||
testing.expectEqual(document, imported.firstChild.ownerDocument);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -444,7 +444,7 @@ pub fn before(self: *CData, nodes: []const Node.NodeOrText, frame: *Frame) !void
|
||||
const parent = node.parentNode() orelse return;
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(node.getDocument(frame));
|
||||
_ = try parent.insertBefore(child, node, frame);
|
||||
}
|
||||
}
|
||||
@@ -455,7 +455,7 @@ pub fn after(self: *CData, nodes: []const Node.NodeOrText, frame: *Frame) !void
|
||||
const viable_next = Node.NodeOrText.viableNextSibling(node, nodes);
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(node.getDocument(frame));
|
||||
_ = try parent.insertBefore(child, viable_next, frame);
|
||||
}
|
||||
}
|
||||
@@ -466,7 +466,7 @@ pub fn replaceWith(self: *CData, nodes: []const Node.NodeOrText, frame: *Frame)
|
||||
|
||||
var rm_ref_node = true;
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(ref_node.getDocument(frame));
|
||||
if (child == ref_node) {
|
||||
rm_ref_node = false;
|
||||
continue;
|
||||
|
||||
@@ -39,11 +39,7 @@ fn createDocumentType(self: *const DOMImplementation, qualified_name: []const u8
|
||||
}
|
||||
}
|
||||
|
||||
const doctype = try DocumentType.init(qualified_name, public_id, system_id, frame);
|
||||
if (self._document != frame.document) {
|
||||
try frame.setNodeOwnerDocument(doctype.asNode(), self._document);
|
||||
}
|
||||
return doctype;
|
||||
return DocumentType.init(self._document, qualified_name, public_id, system_id, frame);
|
||||
}
|
||||
|
||||
fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, frame: *Frame) !*Document {
|
||||
@@ -53,7 +49,7 @@ fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, fr
|
||||
document._charset = "UTF-8";
|
||||
|
||||
{
|
||||
const doctype = try frame._factory.node(DocumentType{
|
||||
const doctype = try frame._factory.node(document, DocumentType{
|
||||
._proto = undefined,
|
||||
._name = "html",
|
||||
._public_id = "",
|
||||
@@ -62,20 +58,20 @@ fn createHTMLDocument(_: *const DOMImplementation, title: ?js.NullableString, fr
|
||||
_ = try document.asNode().appendChild(doctype.asNode(), frame);
|
||||
}
|
||||
|
||||
const html_node = try Frame.node_factory.createElementNS(frame, .html, "html", null);
|
||||
const html_node = try Frame.node_factory.createElementNS(document, .html, "html", null);
|
||||
_ = try document.asNode().appendChild(html_node, frame);
|
||||
|
||||
const head_node = try Frame.node_factory.createElementNS(frame, .html, "head", null);
|
||||
const head_node = try Frame.node_factory.createElementNS(document, .html, "head", null);
|
||||
_ = try html_node.appendChild(head_node, frame);
|
||||
|
||||
if (title) |t| {
|
||||
const title_node = try Frame.node_factory.createElementNS(frame, .html, "title", null);
|
||||
const title_node = try Frame.node_factory.createElementNS(document, .html, "title", null);
|
||||
_ = try head_node.appendChild(title_node, frame);
|
||||
const text_node = try Frame.node_factory.createTextNode(frame, t.value);
|
||||
const text_node = try Frame.node_factory.createTextNode(document, t.value);
|
||||
_ = try title_node.appendChild(text_node, frame);
|
||||
}
|
||||
|
||||
const body_node = try Frame.node_factory.createElementNS(frame, .html, "body", null);
|
||||
const body_node = try Frame.node_factory.createElementNS(document, .html, "body", null);
|
||||
_ = try html_node.appendChild(body_node, frame);
|
||||
|
||||
return document;
|
||||
@@ -119,7 +115,7 @@ fn createDocument(_: *const DOMImplementation, namespace_nullable: js.Nullable([
|
||||
// Create and append root element if qualified_name provided
|
||||
if (qname.len > 0) {
|
||||
const namespace = Node.Element.Namespace.parse(namespace_);
|
||||
const root = try Frame.node_factory.createElementNS(frame, namespace, qname, null);
|
||||
const root = try Frame.node_factory.createElementNS(document, namespace, qname, null);
|
||||
|
||||
// Store the original URI for unknown namespaces so namespaceURI and
|
||||
// lookupNamespaceURI can return it (mirrors Document.createElementNS).
|
||||
|
||||
@@ -59,11 +59,6 @@ pub fn parseFromString(
|
||||
frame._parse_mode = .fragment;
|
||||
defer frame._parse_mode = previous_parse_mode;
|
||||
|
||||
// No browsing context, so no custom element registry.
|
||||
const previous_creation = frame._custom_element_creation;
|
||||
frame._custom_element_creation = .undefined;
|
||||
defer frame._custom_element_creation = previous_creation;
|
||||
|
||||
// Create a new HTMLDocument
|
||||
const doc = try frame._factory.document(HTMLDocument{
|
||||
._proto = undefined,
|
||||
@@ -112,9 +107,9 @@ const parsererror_ns = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
|
||||
// <parsererror> in the Mozilla error namespace.
|
||||
fn parserErrorDocument(frame: *Frame) !*Document.XMLDocument {
|
||||
const doc = try frame._factory.document(Document.XMLDocument{ ._proto = undefined });
|
||||
const root = try Frame.node_factory.createElementNS(frame, .unknown, "parsererror", null);
|
||||
const root = try Frame.node_factory.createElementNS(doc.asDocument(), .unknown, "parsererror", null);
|
||||
try frame._element_namespace_uris.put(frame.arena, root.as(Node.Element), parsererror_ns);
|
||||
const text = try Frame.node_factory.createTextNode(frame, "error");
|
||||
const text = try Frame.node_factory.createTextNode(doc.asDocument(), "error");
|
||||
_ = try root.appendChild(text, frame);
|
||||
_ = try doc.asNode().appendChild(root, frame);
|
||||
return doc;
|
||||
|
||||
@@ -24,6 +24,7 @@ const public_suffix_list = @import("../../data/public_suffix_list.zig");
|
||||
|
||||
const URL = @import("../URL.zig");
|
||||
const js = @import("../js/js.zig");
|
||||
const Page = @import("../Page.zig");
|
||||
const Frame = @import("../Frame.zig");
|
||||
const Parser = @import("../parser/Parser.zig");
|
||||
|
||||
@@ -54,6 +55,8 @@ pub const Proto = Node;
|
||||
|
||||
_type: Type,
|
||||
_proto: *Node,
|
||||
_page: *Page,
|
||||
_index: u32, // browser.documents index
|
||||
_frame: ?*Frame = null,
|
||||
_url: ?[:0]const u8 = null, // URL for documents created via DOMImplementation (about:blank)
|
||||
// content type override for documents created via DOMImplementation.createDocument
|
||||
@@ -373,7 +376,7 @@ pub fn createElement(self: *Document, name: []const u8, options_: ?CreateElement
|
||||
};
|
||||
// HTML documents are case-insensitive - lowercase the tag name
|
||||
|
||||
const node = try self.createElementNode(ns, normalized_name, frame);
|
||||
const node = try self.createElementNode(ns, normalized_name);
|
||||
const element = node.as(Element);
|
||||
|
||||
const options = options_ orelse return element;
|
||||
@@ -389,7 +392,7 @@ pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8
|
||||
_ = try validateAndExtract(namespace, name, .element);
|
||||
const ns = Element.Namespace.parse(namespace);
|
||||
// Per spec, createElementNS does NOT lowercase (unlike createElement).
|
||||
const node = try self.createElementNode(ns, name, frame);
|
||||
const node = try self.createElementNode(ns, name);
|
||||
|
||||
// Store original URI for unknown namespaces so lookupNamespaceURI can return it
|
||||
if (ns == .unknown) {
|
||||
@@ -401,40 +404,26 @@ pub fn createElementNS(self: *Document, namespace: ?[]const u8, name: []const u8
|
||||
return node.as(Element);
|
||||
}
|
||||
|
||||
fn createElementNode(self: *Document, ns: Element.Namespace, name: []const u8, frame: *Frame) !*Node {
|
||||
const previous_creation = frame._custom_element_creation;
|
||||
if (self._frame == null) {
|
||||
// a document without a browser context, e.g. DOMParser, has no custom
|
||||
// element registry
|
||||
frame._custom_element_creation = .undefined;
|
||||
}
|
||||
defer frame._custom_element_creation = previous_creation;
|
||||
|
||||
const node = try Frame.node_factory.createElementNS(frame, ns, name, null);
|
||||
|
||||
// Track owner document if it's not the main document
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
fn createElementNode(self: *Document, ns: Element.Namespace, name: []const u8) !*Node {
|
||||
return Frame.node_factory.createElementNS(self, ns, name, null);
|
||||
}
|
||||
|
||||
fn createAttribute(_: *const Document, name: String.Global, frame: *Frame) !?*Element.Attribute {
|
||||
fn createAttribute(self: *const Document, name: String.Global, frame: *Frame) !?*Element.Attribute {
|
||||
try Element.Attribute.validateAttributeName(name.str);
|
||||
return frame._factory.node(Element.Attribute{
|
||||
return frame._factory.node(self, Element.Attribute{
|
||||
._name = name.str,
|
||||
._value = String.empty,
|
||||
._element = null,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn createAttributeNS(_: *const Document, namespace: []const u8, name: String.Global, frame: *Frame) !?*Element.Attribute {
|
||||
pub fn createAttributeNS(self: *const Document, namespace: []const u8, name: String.Global, frame: *Frame) !?*Element.Attribute {
|
||||
if (std.mem.eql(u8, namespace, "http://www.w3.org/1999/xhtml") == false) {
|
||||
log.warn(.not_implemented, "document.createAttributeNS", .{ .namespace = namespace });
|
||||
}
|
||||
|
||||
try Element.Attribute.validateAttributeName(name.str);
|
||||
return frame._factory.node(Element.Attribute{
|
||||
return frame._factory.node(self, Element.Attribute{
|
||||
._name = name.str,
|
||||
._value = String.empty,
|
||||
._element = null,
|
||||
@@ -522,52 +511,26 @@ fn getImplementation(self: *Document, frame: *Frame) !*DOMImplementation {
|
||||
}
|
||||
|
||||
fn createDocumentFragment(self: *Document, frame: *Frame) !*Node.DocumentFragment {
|
||||
const frag = try Node.DocumentFragment.init(frame);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(frag.asNode(), self);
|
||||
}
|
||||
return frag;
|
||||
return Node.DocumentFragment.init(self, frame);
|
||||
}
|
||||
|
||||
pub fn createComment(self: *Document, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = try Frame.node_factory.createComment(frame, data);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
pub fn createComment(self: *Document, data: []const u8) !*Node {
|
||||
return Frame.node_factory.createComment(self, data);
|
||||
}
|
||||
|
||||
pub fn createTextNode(self: *Document, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = try Frame.node_factory.createTextNode(frame, data);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
pub fn createTextNode(self: *Document, data: []const u8) !*Node {
|
||||
return Frame.node_factory.createTextNode(self, data);
|
||||
}
|
||||
|
||||
pub fn createCDATASection(self: *Document, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = switch (self._type) {
|
||||
.html => return error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
||||
.xml => try Frame.node_factory.createCDATASection(frame, data),
|
||||
.generic => try Frame.node_factory.createCDATASection(frame, data),
|
||||
pub fn createCDATASection(self: *Document, data: []const u8) !*Node {
|
||||
return switch (self._type) {
|
||||
.html => error.NotSupported, // cannot create a CDataSection in an HTMLDocument
|
||||
.xml, .generic => Frame.node_factory.createCDATASection(self, data),
|
||||
};
|
||||
// Track owner document if it's not the main document
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn createProcessingInstruction(self: *Document, target: []const u8, data: []const u8, frame: *Frame) !*Node {
|
||||
const node = try Frame.node_factory.createProcessingInstruction(frame, target, data);
|
||||
// Track owner document if it's not the main document
|
||||
if (self != frame.document) {
|
||||
try frame.setNodeOwnerDocument(node, self);
|
||||
}
|
||||
return node;
|
||||
pub fn createProcessingInstruction(self: *Document, target: []const u8, data: []const u8) !*Node {
|
||||
return Frame.node_factory.createProcessingInstruction(self, target, data);
|
||||
}
|
||||
|
||||
const Range = @import("Range.zig");
|
||||
@@ -801,7 +764,7 @@ fn adoptNode(self: *Document, node: *Node, frame: *Frame) !*Node {
|
||||
return error.HierarchyError;
|
||||
}
|
||||
|
||||
const old_owner = node.ownerDocument(frame) orelse frame.document;
|
||||
const old_owner = node.ownerDocument(frame).?;
|
||||
|
||||
if (node._parent) |parent| {
|
||||
frame.removeNode(parent, node, .{ .reconnect_to = null });
|
||||
@@ -814,12 +777,12 @@ fn adoptNode(self: *Document, node: *Node, frame: *Frame) !*Node {
|
||||
return node;
|
||||
}
|
||||
|
||||
fn importNode(_: *const Document, node: *Node, deep_: ?bool, frame: *Frame) !*Node {
|
||||
fn importNode(self: *const Document, node: *Node, deep_: ?bool, frame: *Frame) !*Node {
|
||||
if (node._type == .document) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
return node.cloneNode(deep_, frame);
|
||||
return node.cloneNodeInto(deep_ orelse false, self, frame);
|
||||
}
|
||||
|
||||
pub fn append(self: *Document, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
@@ -829,7 +792,7 @@ pub fn append(self: *Document, nodes: []const Node.NodeOrText, frame: *Frame) !v
|
||||
frame.domChanged();
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self);
|
||||
|
||||
// DocumentFragments are special - append all their children
|
||||
if (child.is(Node.DocumentFragment)) |_| {
|
||||
@@ -855,7 +818,7 @@ pub fn prepend(self: *Document, nodes: []const Node.NodeOrText, frame: *Frame) !
|
||||
var i = nodes.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
const child = try nodes[i].toNode(frame);
|
||||
const child = try nodes[i].toNode(self);
|
||||
|
||||
// DocumentFragments are special - need to insert all their children
|
||||
if (child.is(Node.DocumentFragment)) |frag| {
|
||||
@@ -1089,7 +1052,7 @@ fn writeInternal(self: *Document, text: []const []const u8, append_newline: bool
|
||||
|
||||
// Our implementation is hacky. We'll write to a DocumentFragment, then
|
||||
// append its children.
|
||||
const fragment = try Node.DocumentFragment.init(frame);
|
||||
const fragment = try Node.DocumentFragment.init(self, frame);
|
||||
const fragment_node = fragment.asNode();
|
||||
|
||||
const previous_parse_mode = frame._parse_mode;
|
||||
@@ -1517,9 +1480,9 @@ fn _injectBlank(self: *Document, frame: *Frame) !void {
|
||||
std.debug.assert(self.asNode()._first_child == null);
|
||||
}
|
||||
|
||||
const html = try Frame.node_factory.createElementNS(frame, .html, "html", null);
|
||||
const head = try Frame.node_factory.createElementNS(frame, .html, "head", null);
|
||||
const body = try Frame.node_factory.createElementNS(frame, .html, "body", null);
|
||||
const html = try Frame.node_factory.createElementNS(self, .html, "html", null);
|
||||
const head = try Frame.node_factory.createElementNS(self, .html, "head", null);
|
||||
const body = try Frame.node_factory.createElementNS(self, .html, "body", null);
|
||||
try frame.appendNode(html, head, .{});
|
||||
try frame.appendNode(html, body, .{});
|
||||
try frame.appendNode(self.asNode(), html, .{});
|
||||
@@ -1542,12 +1505,7 @@ pub const JsApi = struct {
|
||||
|
||||
pub const constructor = bridge.constructor(_constructor, .{});
|
||||
fn _constructor(frame: *Frame) !*Document {
|
||||
return frame._factory.node(Document{
|
||||
._proto = undefined,
|
||||
._type = .generic,
|
||||
._url = "about:blank",
|
||||
._charset = "UTF-8",
|
||||
});
|
||||
return frame._factory.genericDocument(.{ .url = "about:blank", .charset = "UTF-8" });
|
||||
}
|
||||
|
||||
pub const onselectionchange = bridge.accessor(Document.getOnSelectionChange, Document.setOnSelectionChange, .{});
|
||||
|
||||
@@ -54,8 +54,8 @@ pub fn as(self: *DocumentFragment, comptime T: type) *T {
|
||||
return self.is(T).?;
|
||||
}
|
||||
|
||||
pub fn init(frame: *Frame) !*DocumentFragment {
|
||||
return frame._factory.node(DocumentFragment{
|
||||
pub fn init(document: *const Node.Document, frame: *Frame) !*DocumentFragment {
|
||||
return frame._factory.node(document, DocumentFragment{
|
||||
._type = .generic,
|
||||
._proto = undefined,
|
||||
});
|
||||
@@ -129,7 +129,7 @@ fn getChildElementCount(self: *DocumentFragment) usize {
|
||||
pub fn append(self: *DocumentFragment, nodes: []const Node.NodeOrText, frame: *Frame) !void {
|
||||
const parent = self.asNode();
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(parent.getDocument(frame));
|
||||
_ = try parent.appendChild(child, frame);
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ pub fn prepend(self: *DocumentFragment, nodes: []const Node.NodeOrText, frame: *
|
||||
var i = nodes.len;
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
const child = try nodes[i].toNode(frame);
|
||||
const child = try nodes[i].toNode(parent.getDocument(frame));
|
||||
_ = try parent.insertBefore(child, parent.firstChild(), frame);
|
||||
}
|
||||
}
|
||||
@@ -171,14 +171,14 @@ pub fn setHTMLUnsafe(self: *DocumentFragment, html: []const u8, frame: *Frame) !
|
||||
return parent.setHTML(html, .{ .allow_declarative_shadow = true }, frame);
|
||||
}
|
||||
|
||||
pub fn cloneFragment(self: *DocumentFragment, deep: bool, frame: *Frame) !*Node {
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
pub fn cloneFragment(self: *DocumentFragment, deep: bool, document: *const Node.Document, frame: *Frame) !*Node {
|
||||
const fragment = try DocumentFragment.init(document, frame);
|
||||
const fragment_node = fragment.asNode();
|
||||
|
||||
if (deep) {
|
||||
var child_it = self.asNode().childrenIterator();
|
||||
while (child_it.next()) |child| {
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned_child| {
|
||||
if (try child.cloneNodeForAppending(true, document, frame)) |cloned_child| {
|
||||
try frame.appendNode(fragment_node, cloned_child, .{});
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,10 @@ pub const JsApi = struct {
|
||||
pub var class_id: bridge.ClassId = undefined;
|
||||
};
|
||||
|
||||
pub const constructor = bridge.constructor(DocumentFragment.init, .{});
|
||||
pub const constructor = bridge.constructor(_constructor, .{});
|
||||
fn _constructor(frame: *Frame) !*DocumentFragment {
|
||||
return init(frame.document, frame);
|
||||
}
|
||||
|
||||
pub const getElementById = bridge.function(_getElementById, .{});
|
||||
fn _getElementById(self: *DocumentFragment, value_: ?js.Value) !?*Element {
|
||||
|
||||
@@ -32,13 +32,13 @@ _name: []const u8,
|
||||
_public_id: []const u8,
|
||||
_system_id: []const u8,
|
||||
|
||||
pub fn init(qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, frame: *Frame) !*DocumentType {
|
||||
pub fn init(document: *const Node.Document, qualified_name: []const u8, public_id: ?[]const u8, system_id: ?[]const u8, frame: *Frame) !*DocumentType {
|
||||
const name = try frame.dupeString(qualified_name);
|
||||
// Firefox converts null to the string "null", not empty string
|
||||
const pub_id = if (public_id) |p| try frame.dupeString(p) else "null";
|
||||
const sys_id = if (system_id) |s| try frame.dupeString(s) else "null";
|
||||
|
||||
return frame._factory.node(DocumentType{
|
||||
return frame._factory.node(document, DocumentType{
|
||||
._proto = undefined,
|
||||
._name = name,
|
||||
._public_id = pub_id,
|
||||
@@ -72,8 +72,8 @@ pub fn isEqualNode(self: *const DocumentType, other: *const DocumentType) bool {
|
||||
std.mem.eql(u8, self._system_id, other._system_id);
|
||||
}
|
||||
|
||||
pub fn clone(self: *const DocumentType, frame: *Frame) !*DocumentType {
|
||||
return .init(self._name, self._public_id, self._system_id, frame);
|
||||
pub fn clone(self: *const DocumentType, document: *const Node.Document, frame: *Frame) !*DocumentType {
|
||||
return .init(document, self._name, self._public_id, self._system_id, frame);
|
||||
}
|
||||
|
||||
pub fn remove(self: *DocumentType, frame: *Frame) !void {
|
||||
|
||||
@@ -221,6 +221,10 @@ pub fn as(self: *Element, comptime T: type) *T {
|
||||
return self.is(T).?;
|
||||
}
|
||||
|
||||
pub fn getDocument(self: *Element, frame: *const Frame) *Node.Document {
|
||||
return self.asNode().getDocument(frame);
|
||||
}
|
||||
|
||||
pub fn asNode(self: *Element) *Node {
|
||||
return Factory.protoOf(self);
|
||||
}
|
||||
@@ -568,7 +572,7 @@ pub fn setOuterHTML(self: *Element, html: []const u8, frame: *Frame) !void {
|
||||
|
||||
var fragment: ?*Node = null;
|
||||
if (html.len > 0) {
|
||||
const frag = (try Node.DocumentFragment.init(frame)).asNode();
|
||||
const frag = (try Node.DocumentFragment.init(node.getDocument(frame), frame)).asNode();
|
||||
// The parent is the parse context (a fragment parent means body).
|
||||
try Frame.parse.fragment(frame, frag, html, .{ .context = parent.is(Element) });
|
||||
fragment = frag;
|
||||
@@ -936,7 +940,7 @@ pub fn insertAdjacentText(
|
||||
error.AdjacentNoParent => return,
|
||||
else => return err,
|
||||
};
|
||||
const text_node = try Frame.node_factory.createTextNode(frame, data);
|
||||
const text_node = try Frame.node_factory.createTextNode(self.getDocument(frame), data);
|
||||
_ = try target_node.insertBefore(text_node, prev_node, frame);
|
||||
}
|
||||
|
||||
@@ -1128,7 +1132,7 @@ pub fn replaceWith(self: *Element, nodes: []const Node.NodeOrText, frame: *Frame
|
||||
var rm_ref_node = true;
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self.getDocument(frame));
|
||||
|
||||
// If a child is the ref node. We keep it at its own current position.
|
||||
if (child == ref_node) {
|
||||
@@ -1290,7 +1294,7 @@ pub fn before(self: *Element, nodes: []const Node.NodeOrText, frame: *Frame) !vo
|
||||
const parent = node.parentNode() orelse return;
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self.getDocument(frame));
|
||||
_ = try parent.insertBefore(child, node, frame);
|
||||
}
|
||||
}
|
||||
@@ -1301,7 +1305,7 @@ pub fn after(self: *Element, nodes: []const Node.NodeOrText, frame: *Frame) !voi
|
||||
const viable_next = Node.NodeOrText.viableNextSibling(node, nodes);
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self.getDocument(frame));
|
||||
_ = try parent.insertBefore(child, viable_next, frame);
|
||||
}
|
||||
}
|
||||
@@ -1868,9 +1872,9 @@ pub fn getElementsByClassName(self: *Element, class_name: []const u8, frame: *Fr
|
||||
return self.asNode().getElementsByClassName(class_name, frame);
|
||||
}
|
||||
|
||||
pub fn clone(self: *Element, deep: bool, frame: *Frame) !*Node {
|
||||
pub fn clone(self: *Element, deep: bool, document: *const Node.Document, frame: *Frame) !*Node {
|
||||
const tag_name = self.getTagNameDump();
|
||||
const node = try Frame.node_factory.createElementNS(frame, self._namespace, tag_name, &self._attributes);
|
||||
const node = try Frame.node_factory.createElementNS(document, self._namespace, tag_name, &self._attributes);
|
||||
|
||||
// A namespace outside the built-in set lives in a side table; the clone
|
||||
// must report the same namespaceURI.
|
||||
@@ -1901,7 +1905,7 @@ pub fn clone(self: *Element, deep: bool, frame: *Frame) !*Node {
|
||||
const cloned_shadow_node = cloned_shadow.asNode();
|
||||
var shadow_child_it = shadow.asNode().childrenIterator();
|
||||
while (shadow_child_it.next()) |child| {
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned_child| {
|
||||
if (try child.cloneNodeForAppending(true, document, frame)) |cloned_child| {
|
||||
try frame.appendNode(cloned_shadow_node, cloned_child, .{});
|
||||
}
|
||||
}
|
||||
@@ -1911,7 +1915,7 @@ pub fn clone(self: *Element, deep: bool, frame: *Frame) !*Node {
|
||||
if (deep) {
|
||||
var child_it = self.asNode().childrenIterator();
|
||||
while (child_it.next()) |child| {
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned_child| {
|
||||
if (try child.cloneNodeForAppending(true, document, frame)) |cloned_child| {
|
||||
try frame.appendNode(node, cloned_child, .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn setBody(self: *HTMLDocument, html: []const u8, frame: *Frame) !void {
|
||||
|
||||
// Build a fresh <body> holding the parsed HTML as its children. Fragment
|
||||
// parsing strips any <html>/<body>/<head> wrappers the author included.
|
||||
const new_body_node = try Frame.node_factory.createElementNS(frame, .html, "body", null);
|
||||
const new_body_node = try Frame.node_factory.createElementNS(self._proto, .html, "body", null);
|
||||
if (html.len > 0) {
|
||||
try Frame.parse.htmlAsChildren(frame, new_body_node, html);
|
||||
}
|
||||
@@ -158,7 +158,7 @@ fn setTitle(self: *HTMLDocument, title: []const u8, frame: *Frame) !void {
|
||||
}
|
||||
|
||||
// No title element found, create one
|
||||
const title_node = try Frame.node_factory.createElementNS(frame, .html, "title", null);
|
||||
const title_node = try Frame.node_factory.createElementNS(self._proto, .html, "title", null);
|
||||
const title_element = title_node.as(Element);
|
||||
|
||||
// Only add text if non-empty
|
||||
@@ -242,7 +242,7 @@ fn getDocType(self: *HTMLDocument, frame: *Frame) !*DocumentType {
|
||||
}
|
||||
}
|
||||
|
||||
self._document_type = try frame._factory.node(DocumentType{
|
||||
self._document_type = try frame._factory.node(self._proto, DocumentType{
|
||||
._proto = undefined,
|
||||
._name = "html",
|
||||
._public_id = "",
|
||||
|
||||
+48
-68
@@ -52,14 +52,12 @@ _parent: ?*Node = null,
|
||||
_first_child: ?*Node = null,
|
||||
_next: ?*Node = null,
|
||||
_prev: ?*Node = null,
|
||||
_owner: u32 = undefined, // owner document index
|
||||
// In debug, set so that we can check that we have a proper contiguous block
|
||||
// of memory for the entire chain (and thus, simple pointer arithmetics will
|
||||
// work to resolve the proto).
|
||||
_proto_canary: if (lp.IS_DEBUG) *EventTarget else void = undefined,
|
||||
|
||||
// Lookup for nodes that have a different owner document than frame.document
|
||||
pub const OwnerDocumentLookup = std.AutoHashMapUnmanaged(*Node, *Document);
|
||||
|
||||
pub const Type = enum(u8) {
|
||||
cdata,
|
||||
element,
|
||||
@@ -708,48 +706,26 @@ pub fn ownerDocument(self: *const Node, frame: *const Frame) ?*Document {
|
||||
return null;
|
||||
}
|
||||
|
||||
// An attribute node has no parent; its owner follows its element's
|
||||
// (including across adoption into another document).
|
||||
// An attribute node follows its element (including across adoption).
|
||||
if (self._type == .attribute) {
|
||||
if (self.subtype(Element.Attribute)._element) |element| {
|
||||
return element.asNode().ownerDocument(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// The root of the tree that a node belongs to is its owner.
|
||||
var current = self;
|
||||
while (current._parent) |parent| {
|
||||
current = parent;
|
||||
}
|
||||
|
||||
// If the root is a document, then that's our owner.
|
||||
if (current._type == .document) {
|
||||
return current.subtype(Document);
|
||||
}
|
||||
|
||||
// A shadow tree's root is a parent-less ShadowRoot fragment; its owner
|
||||
// is the host's owner document.
|
||||
if (current._type == .document_fragment) {
|
||||
if (current.subtype(DocumentFragment).is(ShadowRoot)) |sr| {
|
||||
return sr._host.asNode().ownerDocument(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, this is a detached node. Check if it has a specific owner
|
||||
// document registered (for nodes created via non-main documents).
|
||||
if (frame._node_owner_documents.get(@constCast(self))) |owner| {
|
||||
return owner;
|
||||
}
|
||||
|
||||
// Default to the main document for detached nodes without a specific owner.
|
||||
return frame.document;
|
||||
// The table is the browser's, so any frame of it will do.
|
||||
return frame._session.browser.documents.get(self._owner);
|
||||
}
|
||||
|
||||
fn ownerDocumentIncludingSelf(self: *const Node, frame: *const Frame) ?*Document {
|
||||
// The spec's "node document": every node has one, a document's is itself.
|
||||
// `frame` only reaches the registry; any frame will do.
|
||||
pub fn getDocument(self: *const Node, frame: *const Frame) *Document {
|
||||
if (self._type == .document) {
|
||||
return self.subtype(Document);
|
||||
}
|
||||
return self.ownerDocument(frame);
|
||||
const doc = self.ownerDocument(frame);
|
||||
lp.assert(doc != null, "null node document", .{ .type = self.getNodeType() });
|
||||
return doc.?;
|
||||
}
|
||||
|
||||
// Returns the Frame that owns this node's tree, or null when the node's
|
||||
@@ -757,9 +733,11 @@ fn ownerDocumentIncludingSelf(self: *const Node, frame: *const Frame) ?*Document
|
||||
// whose frame has since navigated away. Used to tie per-frame state (the
|
||||
// StyleManager, live-collection versions, the event manager, ...) to the
|
||||
// right frame: cross-realm callers must not use the calling frame's.
|
||||
//
|
||||
// `frame` only locates the document table; it is never returned as a
|
||||
// fallback.
|
||||
pub fn ownerFrame(self: *const Node, frame: *const Frame) ?*Frame {
|
||||
const doc = self.ownerDocumentIncludingSelf(frame) orelse return null;
|
||||
return doc._frame;
|
||||
return self.getDocument(frame)._frame;
|
||||
}
|
||||
|
||||
pub const ResolveURLOpts = struct {
|
||||
@@ -770,11 +748,10 @@ pub const ResolveURLOpts = struct {
|
||||
// Uses the document's charset for query string encoding (with NCR fallback for unmappable chars).
|
||||
pub fn resolveURL(self: *const Node, url: anytype, frame: *Frame, opts: ResolveURLOpts) ![:0]const u8 {
|
||||
const allocator = opts.allocator orelse frame.call_arena;
|
||||
const doc: ?*const Document = self.ownerDocumentIncludingSelf(frame);
|
||||
const encoding = if (doc) |d| d.getCharset() else frame.charset;
|
||||
const doc = self.getDocument(frame);
|
||||
// A frameless document (DOMParser, XHR) has no <base>; its URL is the base.
|
||||
const base = if (self.ownerFrame(frame)) |owner| owner.base() else if (doc) |d| d.getURL(frame) else frame.url;
|
||||
return URL.resolve(allocator, base, url, .{ .encoding = encoding });
|
||||
const base = if (doc._frame) |owner| owner.base() else doc.getURL(frame);
|
||||
return URL.resolve(allocator, base, url, .{ .encoding = doc.getCharset() });
|
||||
}
|
||||
|
||||
// Same as `resolveURL` but can't return `TypeError`, this is needed for multiple
|
||||
@@ -787,10 +764,7 @@ pub fn resolveURLReflect(self: *const Node, url: []const u8, frame: *Frame, opts
|
||||
}
|
||||
|
||||
pub fn isSameDocumentAs(self: *const Node, other: *const Node, frame: *const Frame) bool {
|
||||
// Get the root document for each node
|
||||
const self_doc = self.ownerDocumentIncludingSelf(frame);
|
||||
const other_doc = other.ownerDocumentIncludingSelf(frame);
|
||||
return self_doc == other_doc;
|
||||
return self.getDocument(frame) == other.getDocument(frame);
|
||||
}
|
||||
|
||||
pub fn hasChildNodes(self: *const Node) bool {
|
||||
@@ -1206,25 +1180,31 @@ const CloneError = error{
|
||||
CompilationError,
|
||||
JsException,
|
||||
ExecutionTerminated,
|
||||
QuotaExceeded,
|
||||
};
|
||||
pub fn cloneNode(self: *Node, deep_: ?bool, frame: *Frame) CloneError!*Node {
|
||||
return self.cloneNodeInto(deep_ orelse false, self.getDocument(frame), frame);
|
||||
}
|
||||
|
||||
// The spec's clone: the copy belongs to `document`, importNode's target or
|
||||
// the original's own document.
|
||||
pub fn cloneNodeInto(self: *Node, deep: bool, document: *const Document, frame: *Frame) CloneError!*Node {
|
||||
if (self.is(ShadowRoot) != null) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
const deep = deep_ orelse false;
|
||||
switch (self._type) {
|
||||
.cdata => {
|
||||
const cd = self.subtype(CData);
|
||||
const data = cd.getData().str();
|
||||
return switch (cd._type) {
|
||||
.text => Frame.node_factory.createTextNode(frame, data),
|
||||
.cdata_section => Frame.node_factory.createCDATASection(frame, data),
|
||||
.comment => Frame.node_factory.createComment(frame, data),
|
||||
.processing_instruction => Frame.node_factory.createProcessingInstruction(frame, cd.subtype(CData.ProcessingInstruction)._target, data),
|
||||
.text => Frame.node_factory.createTextNode(document, data),
|
||||
.cdata_section => Frame.node_factory.createCDATASection(document, data),
|
||||
.comment => Frame.node_factory.createComment(document, data),
|
||||
.processing_instruction => Frame.node_factory.createProcessingInstruction(document, cd.subtype(CData.ProcessingInstruction)._target, data),
|
||||
};
|
||||
},
|
||||
.element => return self.subtype(Element).clone(deep, frame),
|
||||
.element => return self.subtype(Element).clone(deep, document, frame),
|
||||
.document => {
|
||||
const doc = self.subtype(Document);
|
||||
const cloned = switch (doc._type) {
|
||||
@@ -1237,7 +1217,7 @@ pub fn cloneNode(self: *Node, deep_: ?bool, frame: *Frame) CloneError!*Node {
|
||||
if (deep) {
|
||||
var child_it = self.childrenIterator();
|
||||
while (child_it.next()) |child| {
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned_child| {
|
||||
if (try child.cloneNodeForAppending(true, cloned, frame)) |cloned_child| {
|
||||
_ = cloned.asNode().appendChild(cloned_child, frame) catch return error.CloneError;
|
||||
}
|
||||
}
|
||||
@@ -1245,12 +1225,12 @@ pub fn cloneNode(self: *Node, deep_: ?bool, frame: *Frame) CloneError!*Node {
|
||||
return cloned.asNode();
|
||||
},
|
||||
.document_type => {
|
||||
const cloned = self.subtype(DocumentType).clone(frame) catch return error.CloneError;
|
||||
const cloned = self.subtype(DocumentType).clone(document, frame) catch return error.CloneError;
|
||||
return cloned.asNode();
|
||||
},
|
||||
.document_fragment => return self.subtype(DocumentFragment).cloneFragment(deep, frame),
|
||||
.document_fragment => return self.subtype(DocumentFragment).cloneFragment(deep, document, frame),
|
||||
.attribute => {
|
||||
const cloned = self.subtype(Element.Attribute).clone(frame) catch return error.CloneError;
|
||||
const cloned = self.subtype(Element.Attribute).clone(document, frame) catch return error.CloneError;
|
||||
return cloned.asNode();
|
||||
},
|
||||
}
|
||||
@@ -1262,7 +1242,7 @@ pub fn cloneNode(self: *Node, deep_: ?bool, frame: *Frame) CloneError!*Node {
|
||||
///
|
||||
/// This helper is used when iterating over children to clone them. The typical pattern is:
|
||||
/// while (child_it.next()) |child| {
|
||||
/// if (try child.cloneNodeForAppending(true, frame)) |cloned| {
|
||||
/// if (try child.cloneNodeForAppending(true, document, frame)) |cloned| {
|
||||
/// try frame.appendNode(parent, cloned, opts);
|
||||
/// }
|
||||
/// }
|
||||
@@ -1271,8 +1251,8 @@ pub fn cloneNode(self: *Node, deep_: ?bool, frame: *Frame) CloneError!*Node {
|
||||
/// constructor (which runs during cloning per the HTML spec) explicitly attaches the element
|
||||
/// somewhere. In that case, we respect the constructor's decision and return null to signal
|
||||
/// that the cloned node should not be appended to our intended parent.
|
||||
pub fn cloneNodeForAppending(self: *Node, deep: bool, frame: *Frame) CloneError!?*Node {
|
||||
const cloned = try self.cloneNode(deep, frame);
|
||||
pub fn cloneNodeForAppending(self: *Node, deep: bool, document: *const Document, frame: *Frame) CloneError!?*Node {
|
||||
const cloned = try self.cloneNodeInto(deep, document, frame);
|
||||
if (cloned._parent != null) {
|
||||
return null;
|
||||
}
|
||||
@@ -1479,7 +1459,7 @@ pub fn getElementsByClassName(self: *Node, class_name: []const u8, frame: *Frame
|
||||
try class_names.append(arena, try frame.dupeString(name));
|
||||
}
|
||||
|
||||
const quirks = if (self.ownerDocumentIncludingSelf(frame)) |doc| doc.isQuirksMode() else false;
|
||||
const quirks = self.getDocument(frame).isQuirksMode();
|
||||
return collections.NodeLive(.class_name).init(self, .{
|
||||
.names = class_names.items,
|
||||
.case_insensitive = quirks,
|
||||
@@ -1491,11 +1471,11 @@ pub fn getElementsByClassName(self: *Node, class_name: []const u8, frame: *Frame
|
||||
// observe its later siblings inserted (and can remove them before they run).
|
||||
pub fn appendNodes(self: *Node, nodes: []const NodeOrText, frame: *Frame) !void {
|
||||
if (nodes.len == 1) {
|
||||
const child = try nodes[0].toNode(frame);
|
||||
const child = try nodes[0].toNode(self.getDocument(frame));
|
||||
_ = try self.appendChild(child, frame);
|
||||
return;
|
||||
}
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
const fragment = try DocumentFragment.init(self.getDocument(frame), frame);
|
||||
const fragment_node = fragment.asNode();
|
||||
// The fragment is internal — JS never sees it, and no mutation record
|
||||
// targets it — so it can be reclaimed once its children have moved out.
|
||||
@@ -1503,7 +1483,7 @@ pub fn appendNodes(self: *Node, nodes: []const NodeOrText, frame: *Frame) !void
|
||||
// it (per spec), so it must live on.
|
||||
defer if (fragment_node.firstChild() == null) frame._factory.destroy(fragment);
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self.getDocument(frame));
|
||||
_ = try fragment_node.appendChild(child, frame);
|
||||
}
|
||||
_ = try self.appendChild(fragment_node, frame);
|
||||
@@ -1511,15 +1491,15 @@ pub fn appendNodes(self: *Node, nodes: []const NodeOrText, frame: *Frame) !void
|
||||
|
||||
pub fn prependNodes(self: *Node, nodes: []const NodeOrText, frame: *Frame) !void {
|
||||
if (nodes.len == 1) {
|
||||
const child = try nodes[0].toNode(frame);
|
||||
const child = try nodes[0].toNode(self.getDocument(frame));
|
||||
_ = try self.insertBefore(child, self.firstChild(), frame);
|
||||
return;
|
||||
}
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
const fragment = try DocumentFragment.init(self.getDocument(frame), frame);
|
||||
const fragment_node = fragment.asNode();
|
||||
defer if (fragment_node.firstChild() == null) frame._factory.destroy(fragment);
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self.getDocument(frame));
|
||||
_ = try fragment_node.appendChild(child, frame);
|
||||
}
|
||||
// The reference child is evaluated after converting nodes into the
|
||||
@@ -1535,7 +1515,7 @@ pub fn replaceChildren(self: *Node, nodes: []const NodeOrText, frame: *Frame) !v
|
||||
var children_to_add: std.ArrayList(*Node) = .empty;
|
||||
|
||||
for (nodes) |node_or_text| {
|
||||
const child = try node_or_text.toNode(frame);
|
||||
const child = try node_or_text.toNode(self.getDocument(frame));
|
||||
|
||||
// DocumentFragments contribute their children, not themselves
|
||||
if (child.is(DocumentFragment)) |frag| {
|
||||
@@ -1790,7 +1770,7 @@ pub const JsApi = struct {
|
||||
|
||||
pub const baseURI = bridge.accessor(_baseURI, null, .{});
|
||||
fn _baseURI(self: *Node, frame: *const Frame) []const u8 {
|
||||
const doc = self.ownerDocumentIncludingSelf(frame) orelse return frame.base();
|
||||
const doc = self.getDocument(frame);
|
||||
if (doc._frame) |doc_frame| {
|
||||
return doc_frame.base();
|
||||
}
|
||||
@@ -1842,10 +1822,10 @@ pub const NodeOrText = union(enum) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toNode(self: *const NodeOrText, frame: *Frame) !*Node {
|
||||
pub fn toNode(self: *const NodeOrText, document: *const Document) !*Node {
|
||||
return switch (self.*) {
|
||||
.node => |n| n,
|
||||
.text => |txt| Frame.node_factory.createTextNode(frame, txt),
|
||||
.text => |txt| Frame.node_factory.createTextNode(document, txt),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -479,7 +479,7 @@ fn nodeContained(self: *const Range, node: *Node) bool {
|
||||
}
|
||||
|
||||
fn cloneContents(self: *const Range, frame: *Frame) !*DocumentFragment {
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
const fragment = try DocumentFragment.init(self._proto._start_container.getDocument(frame), frame);
|
||||
if (self._proto.getCollapsed()) return fragment;
|
||||
|
||||
try cloneContentsBetween(
|
||||
@@ -499,7 +499,7 @@ fn cloneContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_offs
|
||||
if (start_node == end_node) {
|
||||
if (start_node.is(Node.CData)) |cdata| {
|
||||
const data = cdata.getData().str();
|
||||
const cloned = (try start_node.cloneNodeForAppending(false, frame)) orelse return;
|
||||
const cloned = (try start_node.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse return;
|
||||
try cloned.setData(data[byteOffset(data, start_offset)..byteOffset(data, end_offset)], frame);
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
return;
|
||||
@@ -522,11 +522,11 @@ fn cloneContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_offs
|
||||
if (c.is(Node.CData)) |cdata| {
|
||||
// c is the start node itself.
|
||||
const data = cdata.getData().str();
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse continue;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse continue;
|
||||
try cloned.setData(data[byteOffset(data, start_offset)..], frame);
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
} else {
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse continue;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse continue;
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
try cloneContentsBetween(frame, cloned, start_node, start_offset, c, c.getLength());
|
||||
}
|
||||
@@ -535,11 +535,11 @@ fn cloneContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_offs
|
||||
if (c.is(Node.CData)) |cdata| {
|
||||
// c is the end node itself.
|
||||
const data = cdata.getData().str();
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse continue;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse continue;
|
||||
try cloned.setData(data[0..byteOffset(data, end_offset)], frame);
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
} else {
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse continue;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse continue;
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
try cloneContentsBetween(frame, cloned, c, 0, end_node, end_offset);
|
||||
}
|
||||
@@ -547,7 +547,7 @@ fn cloneContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_offs
|
||||
if (c._type == .document_type) {
|
||||
return error.HierarchyError;
|
||||
}
|
||||
const cloned = (try c.cloneNodeForAppending(true, frame)) orelse continue;
|
||||
const cloned = (try c.cloneNodeForAppending(true, out.getDocument(frame), frame)) orelse continue;
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
}
|
||||
}
|
||||
@@ -560,7 +560,7 @@ fn containedBetween(node: *Node, start_node: *Node, start_offset: u32, end_node:
|
||||
}
|
||||
|
||||
fn extractContents(self: *Range, frame: *Frame) !*DocumentFragment {
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
const fragment = try DocumentFragment.init(self._proto._start_container.getDocument(frame), frame);
|
||||
if (self._proto.getCollapsed()) return fragment;
|
||||
|
||||
frame.domChanged();
|
||||
@@ -599,7 +599,7 @@ fn extractContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_of
|
||||
if (start_node == end_node) {
|
||||
if (start_node.is(Node.CData)) |cdata| {
|
||||
const data = cdata.getData().str();
|
||||
const cloned = (try start_node.cloneNodeForAppending(false, frame)) orelse return;
|
||||
const cloned = (try start_node.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse return;
|
||||
try cloned.setData(data[byteOffset(data, start_offset)..byteOffset(data, end_offset)], frame);
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
try cdata.replaceData(start_offset, end_offset - start_offset, "", frame);
|
||||
@@ -635,13 +635,13 @@ fn extractContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_of
|
||||
// c is the start node itself.
|
||||
const data = cdata.getData().str();
|
||||
const byte_start = byteOffset(data, start_offset);
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse return;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse return;
|
||||
try cloned.setData(data[byte_start..], frame);
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
const length: u32 = @intCast(cdata.getLength());
|
||||
try cdata.replaceData(start_offset, length - start_offset, "", frame);
|
||||
} else {
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse return;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse return;
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
try extractContentsBetween(frame, cloned, start_node, start_offset, c, c.getLength());
|
||||
}
|
||||
@@ -655,12 +655,12 @@ fn extractContentsBetween(frame: *Frame, out: *Node, start_node: *Node, start_of
|
||||
if (c.is(Node.CData)) |cdata| {
|
||||
// c is the end node itself.
|
||||
const data = cdata.getData().str();
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse return;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse return;
|
||||
try cloned.setData(data[0..byteOffset(data, end_offset)], frame);
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
try cdata.replaceData(0, end_offset, "", frame);
|
||||
} else {
|
||||
const cloned = (try c.cloneNodeForAppending(false, frame)) orelse return;
|
||||
const cloned = (try c.cloneNodeForAppending(false, out.getDocument(frame), frame)) orelse return;
|
||||
_ = try out.appendChild(cloned, frame);
|
||||
try extractContentsBetween(frame, cloned, c, 0, end_node, end_offset);
|
||||
}
|
||||
@@ -689,7 +689,8 @@ fn createContextualFragment(self: *const Range, html: []const u8, frame: *Frame)
|
||||
context_node = context_node.parentNode() orelse context_node;
|
||||
}
|
||||
|
||||
const fragment = try DocumentFragment.init(frame);
|
||||
const document = context_node.getDocument(frame);
|
||||
const fragment = try DocumentFragment.init(document, frame);
|
||||
|
||||
if (html.len == 0) {
|
||||
return fragment;
|
||||
@@ -698,9 +699,9 @@ fn createContextualFragment(self: *const Range, html: []const u8, frame: *Frame)
|
||||
// Create a temporary element of the same type as the context for parsing
|
||||
// This preserves the parsing context without modifying the original node
|
||||
const temp_node = if (context_node.is(Node.Element)) |el|
|
||||
try Frame.node_factory.createElementNS(frame, el._namespace, el.getTagNameLower(), null)
|
||||
try Frame.node_factory.createElementNS(document, el._namespace, el.getTagNameLower(), null)
|
||||
else
|
||||
try Frame.node_factory.createElementNS(frame, .html, "div", null);
|
||||
try Frame.node_factory.createElementNS(document, .html, "div", null);
|
||||
|
||||
try Frame.parse.contextualFragment(frame, temp_node, html);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ _removed_ids: std.StringHashMapUnmanaged(void) = .{},
|
||||
_adopted_style_sheets: ?js.Object.Global = null,
|
||||
|
||||
pub fn init(host: *Element, opts: AttachOptions, frame: *Frame) !*ShadowRoot {
|
||||
return frame._factory.documentFragment(ShadowRoot{
|
||||
return frame._factory.documentFragment(host.getDocument(frame), ShadowRoot{
|
||||
._proto = undefined,
|
||||
._mode = opts.mode,
|
||||
._host = host,
|
||||
|
||||
@@ -31,7 +31,7 @@ _pad: bool = false,
|
||||
_proto_canary: if (lp.IS_DEBUG) *CData else void = undefined,
|
||||
|
||||
pub fn init(str: ?js.NullableString, frame: *Frame) !*Comment {
|
||||
const node = try Frame.node_factory.createComment(frame, if (str) |s| s.value else "");
|
||||
const node = try Frame.node_factory.createComment(frame.document, if (str) |s| s.value else "");
|
||||
return node.as(Comment);
|
||||
}
|
||||
|
||||
|
||||
@@ -40,19 +40,19 @@ pub fn asCData(self: *const Text) *CData {
|
||||
}
|
||||
|
||||
pub fn init(str: ?js.NullableString, frame: *Frame) !*Text {
|
||||
const node = try Frame.node_factory.createTextNode(frame, if (str) |s| s.value else "");
|
||||
const node = try Frame.node_factory.createTextNode(frame.document, if (str) |s| s.value else "");
|
||||
return node.as(Text);
|
||||
}
|
||||
|
||||
// This Text node's own data (getWholeText below spans adjacent Text nodes).
|
||||
pub fn ownData(self: *const Text) []const u8 {
|
||||
return Factory.protoOf(self)._data.str();
|
||||
return self.asCData()._data.str();
|
||||
}
|
||||
|
||||
// The concatenated data of the contiguous exclusive Text nodes (adjacent
|
||||
// Text siblings on both sides of this one), in tree order.
|
||||
fn getWholeText(self: *Text, frame: *Frame) ![]const u8 {
|
||||
const node = Factory.protoOf(self).asNode();
|
||||
const node = self.asCData().asNode();
|
||||
|
||||
var first = node;
|
||||
while (first.previousSibling()) |prev| {
|
||||
@@ -86,16 +86,17 @@ fn getAssignedSlot(self: *Text, frame: *Frame) ?*Slot {
|
||||
}
|
||||
|
||||
pub fn splitText(self: *Text, offset: usize, frame: *Frame) !*Text {
|
||||
const data = Factory.protoOf(self)._data.str();
|
||||
const cdata = self.asCData();
|
||||
const node = cdata.asNode();
|
||||
|
||||
const data = cdata._data.str();
|
||||
|
||||
const byte_offset = CData.utf16OffsetToUtf8(data, offset) catch return error.IndexSizeError;
|
||||
|
||||
const new_data = data[byte_offset..];
|
||||
const new_node = try Frame.node_factory.createTextNode(frame, new_data);
|
||||
const new_node = try Frame.node_factory.createTextNode(node.getDocument(frame), new_data);
|
||||
const new_text = new_node.as(Text);
|
||||
|
||||
const node = Factory.protoOf(self).asNode();
|
||||
|
||||
// Per DOM spec splitText: insert first (step 7a), then update ranges (7b-7e),
|
||||
// then truncate original node (step 8).
|
||||
if (node.parentNode()) |parent| {
|
||||
@@ -112,7 +113,7 @@ pub fn splitText(self: *Text, offset: usize, frame: *Frame) !*Text {
|
||||
// Use replaceData instead of setData so live range updates fire
|
||||
// (matters for detached text nodes where steps 7b-7e were skipped).
|
||||
const length = Factory.protoOf(self).getLength();
|
||||
try Factory.protoOf(self).replaceData(offset, length - offset, "", frame);
|
||||
try cdata.replaceData(offset, length - offset, "", frame);
|
||||
|
||||
return new_text;
|
||||
}
|
||||
|
||||
@@ -88,20 +88,12 @@ pub fn isEqualNode(self: *const Attribute, other: *const Attribute) bool {
|
||||
return self.getName().eql(other.getName()) and self.getValue().eql(other.getValue());
|
||||
}
|
||||
|
||||
pub fn clone(self: *const Attribute, frame: *Frame) !*Attribute {
|
||||
const cloned = try frame._factory.node(Attribute{
|
||||
pub fn clone(self: *const Attribute, document: *const Node.Document, frame: *Frame) !*Attribute {
|
||||
return frame._factory.node(document, Attribute{
|
||||
._element = null,
|
||||
._name = self._name,
|
||||
._value = self._value,
|
||||
});
|
||||
|
||||
if (self._element) |el| {
|
||||
// cloned has no element, we need to store its document
|
||||
if (el.asNode().ownerDocument(frame)) |doc| {
|
||||
try frame.setNodeOwnerDocument(cloned.asNode(), doc);
|
||||
}
|
||||
}
|
||||
return cloned;
|
||||
}
|
||||
|
||||
pub const JsApi = struct {
|
||||
@@ -212,7 +204,7 @@ pub const List = struct {
|
||||
return self.getEntryWithInternedName(name) != null;
|
||||
}
|
||||
|
||||
pub fn getAttribute(self: *const List, name: String, element: ?*Element, frame: *Frame) !?*Attribute {
|
||||
pub fn getAttribute(self: *const List, name: String, element: *Element, frame: *Frame) !?*Attribute {
|
||||
const entry = (try self.getEntry(name, frame)) orelse return null;
|
||||
return self.getOrCreateAttribute(entry, element, frame);
|
||||
}
|
||||
@@ -220,8 +212,8 @@ pub const List = struct {
|
||||
// Identity map access: a given (list, name) always yields the same
|
||||
// *Attribute until the attribute is removed. The map must be the
|
||||
// element's frame's, not the caller's frame.
|
||||
pub fn getOrCreateAttribute(self: *const List, entry: *const Entry, element: ?*Element, frame: *Frame) !*Attribute {
|
||||
const owner = if (element) |el| (el.ownerFrame(frame) orelse frame) else frame;
|
||||
pub fn getOrCreateAttribute(self: *const List, entry: *const Entry, element: *Element, frame: *Frame) !*Attribute {
|
||||
const owner = element.ownerFrame(frame) orelse frame;
|
||||
const gop = try owner._attribute_lookup.getOrPut(owner.arena, .{ .list = self, .name = entry._name_ptr });
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = try entry.toAttribute(element, owner);
|
||||
@@ -504,8 +496,8 @@ pub const List = struct {
|
||||
return formatAttribute(self.name(), self.value(), writer);
|
||||
}
|
||||
|
||||
fn toAttribute(self: *const Entry, element: ?*Element, frame: *Frame) !*Attribute {
|
||||
return frame._factory.node(Attribute{
|
||||
fn toAttribute(self: *const Entry, element: *Element, frame: *Frame) !*Attribute {
|
||||
return frame._factory.node(element.getDocument(frame), Attribute{
|
||||
._element = element,
|
||||
// The entry's bytes outlive the entry itself, so the
|
||||
// Attribute can wrap them without duping.
|
||||
|
||||
@@ -329,7 +329,7 @@ pub fn getInnerText(self: *HtmlElement, writer: *std.Io.Writer, frame: *Frame) !
|
||||
}
|
||||
|
||||
pub fn setInnerText(self: *HtmlElement, text: []const u8, frame: *Frame) !void {
|
||||
const items = try renderedTextFragment(text, frame);
|
||||
const items = try renderedTextFragment(self.asNode().getDocument(frame), text, frame);
|
||||
try self.asElement().replaceChildren(items, frame);
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ pub fn setOuterText(self: *HtmlElement, text: []const u8, frame: *Frame) !void {
|
||||
const prev = node.previousSibling();
|
||||
const next = node.nextSibling();
|
||||
|
||||
var items: []const Node.NodeOrText = try renderedTextFragment(text, frame);
|
||||
var items: []const Node.NodeOrText = try renderedTextFragment(node.getDocument(frame), text, frame);
|
||||
if (items.len == 0) {
|
||||
// A fragment with no node still replaces the element with an empty Text
|
||||
// node so surrounding text can merge with it.
|
||||
@@ -389,7 +389,7 @@ pub fn insertAdjacentHTML(
|
||||
else
|
||||
null;
|
||||
|
||||
const fragment = (try DocumentFragment.init(frame)).asNode();
|
||||
const fragment = (try DocumentFragment.init(self.asNode().getDocument(frame), frame)).asNode();
|
||||
try Frame.parse.fragment(frame, fragment, html, .{ .context = context });
|
||||
|
||||
const target_node, const prev_node = try self.asNode().findAdjacentNodes(position, .html);
|
||||
@@ -1797,7 +1797,7 @@ fn mergeTextNodes(left_node: *Node, right_node: *Node, frame: *Frame) !bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
fn renderedTextFragment(value: []const u8, frame: *Frame) ![]Node.NodeOrText {
|
||||
fn renderedTextFragment(document: *const Node.Document, value: []const u8, frame: *Frame) ![]Node.NodeOrText {
|
||||
const arena = frame.local_arena;
|
||||
var nodes: std.ArrayList(Node.NodeOrText) = .empty;
|
||||
|
||||
@@ -1816,7 +1816,7 @@ fn renderedTextFragment(value: []const u8, frame: *Frame) ![]Node.NodeOrText {
|
||||
// break (so "\r\n" is one <br> but "\n\n" is two).
|
||||
const break_len: usize = if (rest[0] == '\r' and rest.len > 1 and rest[1] == '\n') 2 else 1;
|
||||
|
||||
try nodes.append(arena, .{ .node = try Frame.node_factory.createElementNS(frame, .html, "br", null) });
|
||||
try nodes.append(arena, .{ .node = try Frame.node_factory.createElementNS(document, .html, "br", null) });
|
||||
rest = rest[break_len..];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ _pad: bool = false,
|
||||
_proto_canary: if (lp.IS_DEBUG) *Media else void = undefined,
|
||||
|
||||
pub fn constructor(maybe_url: ?String, frame: *Frame) !*Media {
|
||||
const node = try Frame.node_factory.createElementNS(frame, .html, "audio", null);
|
||||
const node = try Frame.node_factory.createElementNS(frame.document, .html, "audio", null);
|
||||
const el = node.as(Element);
|
||||
|
||||
// Always set to "auto" initially.
|
||||
|
||||
@@ -21,7 +21,7 @@ _complete: bool = true,
|
||||
_proto_canary: if (lp.IS_DEBUG) *HtmlElement else void = undefined,
|
||||
|
||||
pub fn constructor(w_: ?u32, h_: ?u32, frame: *Frame) !*Image {
|
||||
const node = try Frame.node_factory.createElementNS(frame, .html, "img", null);
|
||||
const node = try Frame.node_factory.createElementNS(frame.document, .html, "img", null);
|
||||
const el = node.as(Element);
|
||||
|
||||
if (w_) |w| blk: {
|
||||
@@ -97,6 +97,11 @@ fn imageAddedCallback(self: *Image, frame: *Frame) !void {
|
||||
return;
|
||||
}
|
||||
|
||||
// A document without a browsing context (DOMParser et al.) loads nothing.
|
||||
if (self.asElement().getDocument(frame)._frame == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._generation +%= 1;
|
||||
self._complete = true;
|
||||
|
||||
|
||||
@@ -108,6 +108,11 @@ pub fn linkAddedCallback(self: *Link, frame: *Frame) !void {
|
||||
|
||||
const element = self.asElement();
|
||||
|
||||
// A document without a browsing context (DOMParser et al.) loads nothing.
|
||||
if (element.getDocument(frame)._frame == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const href = element.getAttributeInterned("href") orelse return;
|
||||
if (href.len == 0) {
|
||||
return;
|
||||
|
||||
@@ -161,6 +161,11 @@ fn immediateRefreshTarget(content: []const u8) ?[]const u8 {
|
||||
|
||||
pub const Build = struct {
|
||||
pub fn created(node: *Node, frame: *Frame) !void {
|
||||
// A document without a browsing context has no policy or refresh to set.
|
||||
if (node.getDocument(frame)._frame == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const self = node.as(Meta);
|
||||
const el = self.asElement();
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ pub const Build = struct {
|
||||
pub fn created(node: *Node, frame: *Frame) !void {
|
||||
const self = node.as(Template);
|
||||
// Create the template content DocumentFragment
|
||||
self._content = try DocumentFragment.init(frame);
|
||||
self._content = try DocumentFragment.init(node.getDocument(frame), frame);
|
||||
}
|
||||
|
||||
// Per the HTML spec's cloning steps for <template>, a deep clone must
|
||||
@@ -173,7 +173,7 @@ pub const Build = struct {
|
||||
const clone_content = clone._content.asNode();
|
||||
var child_it = source._content.asNode().childrenIterator();
|
||||
while (child_it.next()) |child| {
|
||||
if (try child.cloneNodeForAppending(true, frame)) |cloned_child| {
|
||||
if (try child.cloneNodeForAppending(true, clone_content.getDocument(frame), frame)) |cloned_child| {
|
||||
try frame.appendNode(clone_content, cloned_child, .{});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ fn setDefaultValue(self: *TextArea, value: []const u8, frame: *Frame) !void {
|
||||
}
|
||||
|
||||
// No text child exists, create one
|
||||
const text_node = try Frame.node_factory.createTextNode(frame, value);
|
||||
const text_node = try Frame.node_factory.createTextNode(node.getDocument(frame), value);
|
||||
_ = try node.appendChild(text_node, frame);
|
||||
}
|
||||
|
||||
|
||||
@@ -567,7 +567,7 @@ fn getResponse(self: *XMLHttpRequest, exec: *const Execution) !?Response {
|
||||
if (!final.isHTML()) {
|
||||
return null;
|
||||
}
|
||||
const document = try exec._factory.node(Node.Document{ ._proto = undefined, ._type = .generic });
|
||||
const document = try exec._factory.genericDocument(.{});
|
||||
try Frame.parse.htmlAsChildren(frame, document.asNode(), data);
|
||||
break :blk .{ .document = document };
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user