diff --git a/src/browser/tests/document/document.html b/src/browser/tests/document/document.html index ede2b507..eb69f5d8 100644 --- a/src/browser/tests/document/document.html +++ b/src/browser/tests/document/document.html @@ -380,6 +380,53 @@ testing.expectEqual(0, nd.childElementCount); + + diff --git a/src/browser/tests/element/selector_invalid.html b/src/browser/tests/element/selector_invalid.html index c0d16d59..7112ab85 100644 --- a/src/browser/tests/element/selector_invalid.html +++ b/src/browser/tests/element/selector_invalid.html @@ -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()')); } @@ -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(+)')); } @@ -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)')); } @@ -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 ~')); } diff --git a/src/browser/tests/page/meta.html b/src/browser/tests/page/meta.html index 3c03f403..98fb1688 100644 --- a/src/browser/tests/page/meta.html +++ b/src/browser/tests/page/meta.html @@ -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); diff --git a/src/browser/webapi/DOMImplementation.zig b/src/browser/webapi/DOMImplementation.zig index 777a9571..280db6a8 100644 --- a/src/browser/webapi/DOMImplementation.zig +++ b/src/browser/webapi/DOMImplementation.zig @@ -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); } diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 747e08c2..95c57793 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -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, .{}); diff --git a/src/browser/webapi/DocumentFragment.zig b/src/browser/webapi/DocumentFragment.zig index 186bc68a..b55050f2 100644 --- a/src/browser/webapi/DocumentFragment.zig +++ b/src/browser/webapi/DocumentFragment.zig @@ -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) { diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index 4de1a732..058875a6 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -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 { diff --git a/src/browser/webapi/HTMLDocument.zig b/src/browser/webapi/HTMLDocument.zig index 41782cc8..19e462a1 100644 --- a/src/browser/webapi/HTMLDocument.zig +++ b/src/browser/webapi/HTMLDocument.zig @@ -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, .{}); diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 7df1fd6a..c26411ed 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -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 { diff --git a/src/browser/webapi/selector/Selector.zig b/src/browser/webapi/selector/Selector.zig index 2591ce6c..7322e02e 100644 --- a/src/browser/webapi/selector/Selector.zig +++ b/src/browser/webapi/selector/Selector.zig @@ -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;