mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
Various small DOM fixes, WPT driven
1. Implement document.adoptNode (we were removing from the existing document, but not adding to the new document) 2. Document.url should use the document's frame, falling back to the execution frame 3. Move HTMLDocument.location to Document.location 4. DOMImplementation.createDocument uses a more appropriate default namespace (xml -> null) 5. Map querySelector functions to DOMException-safe errors. The Selector returns specific errors, but for the DOM apis (document.querySelector, df.querySelectorAll, elem.matches, etc...) these largely all map to SyntaxError
This commit is contained in:
@@ -380,6 +380,53 @@
|
||||
testing.expectEqual(0, nd.childElementCount);
|
||||
</script>
|
||||
|
||||
<script id=adoptNode>
|
||||
{
|
||||
// Adopting a Document throws NotSupportedError
|
||||
testing.withError((err) => {
|
||||
testing.expectEqual(9, err.code);
|
||||
testing.expectEqual("NotSupportedError", err.name);
|
||||
}, () => document.adoptNode(document));
|
||||
|
||||
// Same-document adopt: returns the node, clears parent, owner unchanged
|
||||
const el = document.createElement('div');
|
||||
const child = document.createElement('span');
|
||||
el.appendChild(child);
|
||||
document.body.appendChild(el);
|
||||
testing.expectEqual(document.body, el.parentNode);
|
||||
testing.expectEqual(document, el.ownerDocument);
|
||||
testing.expectEqual(document, child.ownerDocument);
|
||||
|
||||
testing.expectEqual(el, document.adoptNode(el));
|
||||
testing.expectEqual(null, el.parentNode);
|
||||
testing.expectEqual(child, el.firstChild);
|
||||
testing.expectEqual(document, el.ownerDocument);
|
||||
testing.expectEqual(document, child.ownerDocument);
|
||||
|
||||
// Cross-document adopt: node + descendants retarget to the new document
|
||||
const otherDoc = new Document();
|
||||
testing.expectEqual(el, otherDoc.adoptNode(el));
|
||||
testing.expectEqual(null, el.parentNode);
|
||||
testing.expectEqual(child, el.firstChild);
|
||||
testing.expectEqual(otherDoc, el.ownerDocument);
|
||||
testing.expectEqual(otherDoc, child.ownerDocument);
|
||||
|
||||
// Round-trip back to the main document
|
||||
testing.expectEqual(el, document.adoptNode(el));
|
||||
testing.expectEqual(document, el.ownerDocument);
|
||||
testing.expectEqual(document, child.ownerDocument);
|
||||
|
||||
// Adopt across documents removes the node from its old parent
|
||||
const orphan = document.createElement('p');
|
||||
document.body.appendChild(orphan);
|
||||
testing.expectEqual(document.body, orphan.parentNode);
|
||||
const otherDoc2 = new Document();
|
||||
testing.expectEqual(orphan, otherDoc2.adoptNode(orphan));
|
||||
testing.expectEqual(null, orphan.parentNode);
|
||||
testing.expectEqual(otherDoc2, orphan.ownerDocument);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id=adoptedStyleSheets>
|
||||
{
|
||||
const acss = document.adoptedStyleSheets;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
document.querySelector('p[data-random="abc\\5C def"]').textContent);
|
||||
|
||||
// A bare newline inside a string token is a parse error.
|
||||
testing.expectError("Error: InvalidAttributeSelector",
|
||||
testing.expectError("SyntaxError",
|
||||
() => document.querySelector('p[data-random="line one\nline two"]'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
const container = $('#container');
|
||||
|
||||
// Empty functional pseudo-classes should error
|
||||
testing.expectError("Error: InvalidPseudoClass", () => container.querySelector(':has()'));
|
||||
testing.expectError("Error: InvalidPseudoClass", () => container.querySelector(':not()'));
|
||||
testing.expectError("Error: InvalidPseudoClass", () => container.querySelector(':lang()'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':has()'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':not()'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':lang()'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
const container = $('#container');
|
||||
|
||||
// Invalid nth patterns
|
||||
testing.expectError("Error: InvalidNthPattern", () => container.querySelector(':nth-child(foo)'));
|
||||
testing.expectError("Error: InvalidNthPattern", () => container.querySelector(':nth-child(-)'));
|
||||
testing.expectError("Error: InvalidNthPattern", () => container.querySelector(':nth-child(+)'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':nth-child(foo)'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':nth-child(-)'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':nth-child(+)'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
const container = $('#container');
|
||||
|
||||
// Unknown pseudo-classes
|
||||
testing.expectError("Error: UnknownPseudoClass", () => container.querySelector(':unknown'));
|
||||
testing.expectError("Error: UnknownPseudoClass", () => container.querySelector(':not-a-real-pseudo'));
|
||||
testing.expectError("Error: UnknownPseudoClass", () => container.querySelector(':fake(test)'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':unknown'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':not-a-real-pseudo'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector(':fake(test)'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
const container = $('#container');
|
||||
|
||||
// Combinators with nothing after
|
||||
testing.expectError("Error: InvalidSelector", () => container.querySelector('p >'));
|
||||
testing.expectError("Error: InvalidSelector", () => container.querySelector('p +'));
|
||||
testing.expectError("Error: InvalidSelector", () => container.querySelector('p ~'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector('p >'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector('p +'));
|
||||
testing.expectError("SyntaxError", () => container.querySelector('p ~'));
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -30,7 +30,8 @@
|
||||
testing.expectEqual('undefined', typeof plainDoc.scripts);
|
||||
testing.expectEqual('undefined', typeof plainDoc.links);
|
||||
testing.expectEqual('undefined', typeof plainDoc.forms);
|
||||
testing.expectEqual('undefined', typeof plainDoc.location);
|
||||
// location lives on Document (returns null for non-HTMLDocument).
|
||||
testing.expectEqual(null, plainDoc.location);
|
||||
|
||||
// Both should have common Document properties
|
||||
testing.expectEqual('string', typeof document.URL);
|
||||
|
||||
@@ -78,7 +78,7 @@ pub fn createDocument(_: *const DOMImplementation, namespace_: ?[]const u8, qual
|
||||
// Create and append root element if qualified_name provided
|
||||
if (qualified_name) |qname| {
|
||||
if (qname.len > 0) {
|
||||
const namespace = if (namespace_) |ns| Node.Element.Namespace.parse(ns) else .xml;
|
||||
const namespace = Node.Element.Namespace.parse(namespace_);
|
||||
const root = try frame.createElementNS(namespace, qname, null);
|
||||
_ = try document.asNode().appendChild(root, frame);
|
||||
}
|
||||
|
||||
@@ -119,7 +119,18 @@ pub fn asEventTarget(self: *Document) *@import("EventTarget.zig") {
|
||||
}
|
||||
|
||||
pub fn getURL(self: *const Document, frame: *const Frame) [:0]const u8 {
|
||||
return self._url orelse frame.url;
|
||||
return self._url orelse (self._frame orelse frame).url;
|
||||
}
|
||||
|
||||
pub fn getLocation(self: *const Document) ?*Location {
|
||||
if (self._type != .html) return null;
|
||||
const doc_frame = self._frame orelse return null;
|
||||
return doc_frame.window._location;
|
||||
}
|
||||
|
||||
pub fn setLocation(self: *Document, url: [:0]const u8, frame: *Frame) !void {
|
||||
if (self._type != .html) return;
|
||||
return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._frame });
|
||||
}
|
||||
|
||||
pub fn getContentType(self: *const Document) []const u8 {
|
||||
@@ -277,11 +288,11 @@ pub fn getSelection(self: *Document) *Selection {
|
||||
}
|
||||
|
||||
pub fn querySelector(self: *Document, input: String, frame: *Frame) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), input.str(), frame);
|
||||
return Selector.querySelector(self.asNode(), input.str(), frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn querySelectorAll(self: *Document, input: String, frame: *Frame) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input.str(), frame);
|
||||
return Selector.querySelectorAll(self.asNode(), input.str(), frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn getImplementation(self: *Document, frame: *Frame) !*DOMImplementation {
|
||||
@@ -465,15 +476,21 @@ pub fn getFonts(self: *Document, frame: *Frame) !*FontFaceSet {
|
||||
return fonts;
|
||||
}
|
||||
|
||||
pub fn adoptNode(_: *const Document, node: *Node, frame: *Frame) !*Node {
|
||||
pub fn adoptNode(self: *Document, node: *Node, frame: *Frame) !*Node {
|
||||
if (node._type == .document) {
|
||||
return error.NotSupported;
|
||||
}
|
||||
|
||||
const old_owner = node.ownerDocument(frame) orelse frame.document;
|
||||
|
||||
if (node._parent) |parent| {
|
||||
frame.removeNode(parent, node, .{ .will_be_reconnected = false });
|
||||
}
|
||||
|
||||
if (old_owner != self) {
|
||||
try frame.adoptNodeTree(node, old_owner, self);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
@@ -1029,6 +1046,7 @@ pub const JsApi = struct {
|
||||
|
||||
pub const onselectionchange = bridge.accessor(Document.getOnSelectionChange, Document.setOnSelectionChange, .{});
|
||||
pub const URL = bridge.accessor(Document.getURL, null, .{});
|
||||
pub const location = bridge.accessor(Document.getLocation, Document.setLocation, .{});
|
||||
pub const documentURI = bridge.accessor(Document.getURL, null, .{});
|
||||
pub const documentElement = bridge.accessor(Document.getDocumentElement, null, .{});
|
||||
pub const scrollingElement = bridge.accessor(Document.getDocumentElement, null, .{});
|
||||
|
||||
@@ -84,11 +84,11 @@ pub fn getElementById(self: *DocumentFragment, id: []const u8) ?*Element {
|
||||
}
|
||||
|
||||
pub fn querySelector(self: *DocumentFragment, selector: []const u8, frame: *Frame) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), selector, frame);
|
||||
return Selector.querySelector(self.asNode(), selector, frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn querySelectorAll(self: *DocumentFragment, input: []const u8, frame: *Frame) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input, frame);
|
||||
return Selector.querySelectorAll(self.asNode(), input, frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn getChildren(self: *DocumentFragment, frame: *Frame) !collections.NodeLive(.child_elements) {
|
||||
|
||||
@@ -1071,15 +1071,15 @@ pub fn getChildElementCount(self: *Element) usize {
|
||||
}
|
||||
|
||||
pub fn matches(self: *Element, selector: []const u8, frame: *Frame) !bool {
|
||||
return Selector.matches(self, selector, frame);
|
||||
return Selector.matches(self, selector, frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn querySelector(self: *Element, selector: []const u8, frame: *Frame) !?*Element {
|
||||
return Selector.querySelector(self.asNode(), selector, frame);
|
||||
return Selector.querySelector(self.asNode(), selector, frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn querySelectorAll(self: *Element, input: []const u8, frame: *Frame) !*Selector.List {
|
||||
return Selector.querySelectorAll(self.asNode(), input, frame);
|
||||
return Selector.querySelectorAll(self.asNode(), input, frame) catch |err| Selector.mapErrorToDOM(err);
|
||||
}
|
||||
|
||||
pub fn getAnimations(_: *const Element) []*Animation {
|
||||
|
||||
@@ -196,15 +196,6 @@ pub fn getCurrentScript(self: *const HTMLDocument) ?*Element.Html.Script {
|
||||
return self._proto._current_script;
|
||||
}
|
||||
|
||||
pub fn getLocation(self: *const HTMLDocument) ?*@import("Location.zig") {
|
||||
const frame = self._proto._frame orelse return null;
|
||||
return frame.window._location;
|
||||
}
|
||||
|
||||
pub fn setLocation(self: *HTMLDocument, url: [:0]const u8, frame: *Frame) !void {
|
||||
return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._proto._frame });
|
||||
}
|
||||
|
||||
pub fn getDir(self: *HTMLDocument) []const u8 {
|
||||
const el = self._proto.getDocumentElement() orelse return "";
|
||||
const html = el.is(Element.Html) orelse return "";
|
||||
@@ -311,7 +302,6 @@ pub const JsApi = struct {
|
||||
pub const applets = bridge.accessor(HTMLDocument.getApplets, null, .{});
|
||||
pub const plugins = bridge.accessor(HTMLDocument.getEmbeds, null, .{});
|
||||
pub const currentScript = bridge.accessor(HTMLDocument.getCurrentScript, null, .{});
|
||||
pub const location = bridge.accessor(HTMLDocument.getLocation, HTMLDocument.setLocation, .{});
|
||||
pub const all = bridge.accessor(HTMLDocument.getAll, null, .{});
|
||||
pub const cookie = bridge.accessor(HTMLDocument.getCookie, HTMLDocument.setCookie, .{});
|
||||
pub const doctype = bridge.accessor(HTMLDocument.getDocType, null, .{});
|
||||
|
||||
@@ -166,7 +166,7 @@ pub fn findAdjacentNodes(self: *Node, position: []const u8) !struct { *Node, ?*N
|
||||
// Returned if:
|
||||
// * position is not one of the four listed values.
|
||||
// * The input is XML that is not well-formed.
|
||||
return error.Syntax;
|
||||
return error.SyntaxError;
|
||||
}
|
||||
|
||||
pub fn firstChild(self: *const Node) ?*Node {
|
||||
|
||||
@@ -28,6 +28,22 @@ pub const List = @import("List.zig");
|
||||
const String = lp.String;
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
// translate a Selector error to a DOMException known type.
|
||||
pub fn mapErrorToDOM(err: anyerror) anyerror {
|
||||
return switch (err) {
|
||||
error.InvalidSelector,
|
||||
error.InvalidAttributeSelector,
|
||||
error.InvalidIDSelector,
|
||||
error.InvalidClassSelector,
|
||||
error.UnknownPseudoClass,
|
||||
error.InvalidTagSelector,
|
||||
error.InvalidPseudoClass,
|
||||
error.InvalidNthPattern,
|
||||
=> error.SyntaxError,
|
||||
else => err,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parseLeaky(arena: Allocator, input: []const u8) !Parsed {
|
||||
if (input.len == 0) {
|
||||
return error.SyntaxError;
|
||||
|
||||
Reference in New Issue
Block a user