diff --git a/src/browser/EventManager.zig b/src/browser/EventManager.zig index 5b0c20a38..370d1ae84 100644 --- a/src/browser/EventManager.zig +++ b/src/browser/EventManager.zig @@ -637,7 +637,7 @@ const ActivationState = struct { // Walk from the root of the tree containing this element // This handles both document-attached and orphaned elements - const root = elem.asNode().getRootNode(null); + const root = elem.asNode().getRootNode(.{}); const TreeWalker = @import("webapi/TreeWalker.zig"); var walker = TreeWalker.Full.init(root, .{}); diff --git a/src/browser/Frame.zig b/src/browser/Frame.zig index 943c820fc..30f2badc3 100644 --- a/src/browser/Frame.zig +++ b/src/browser/Frame.zig @@ -1684,24 +1684,17 @@ pub fn removeElementIdWithMaps(self: *Frame, id_maps: ElementIdMaps, id: []const } pub fn getElementByIdFromNode(self: *Frame, node: *Node, id: []const u8) ?*Element { - if (node.isConnected() or node.isInShadowTree()) { - var current = node; - while (true) { - if (current.is(ShadowRoot)) |shadow_root| { - return shadow_root.getElementById(id, self); - } - const parent = current._parent orelse { - if (current._type == .document) { - return current._type.document.getElementById(id, self); - } - if (IS_DEBUG) { - std.debug.assert(false); - } - return null; - }; - current = parent; - } + // The id map lives on the node's root: a Document, or a ShadowRoot for + // shadow DOM. Walk to the root once and consult the matching map. + const root = node.getRootNode(.{}); + if (root._type == .document) { + return root._type.document.getElementById(id, self); } + if (root.is(ShadowRoot)) |shadow_root| { + return shadow_root.getElementById(id, self); + } + // Detached subtree (root is neither a Document nor a ShadowRoot): no id map + // exists, so scan it. var tw = @import("webapi/TreeWalker.zig").Full.Elements.init(node, .{}); while (tw.next()) |el| { const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse continue; diff --git a/src/browser/tests/frames/cross_realm_focus.html b/src/browser/tests/frames/cross_realm_focus.html new file mode 100644 index 000000000..3bbef9091 --- /dev/null +++ b/src/browser/tests/frames/cross_realm_focus.html @@ -0,0 +1,33 @@ + +
+ + + + + + + + diff --git a/src/browser/tests/frames/cross_realm_identity.html b/src/browser/tests/frames/cross_realm_identity.html new file mode 100644 index 000000000..0256e0cf9 --- /dev/null +++ b/src/browser/tests/frames/cross_realm_identity.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + diff --git a/src/browser/tests/frames/support/cross_realm_focus.html b/src/browser/tests/frames/support/cross_realm_focus.html new file mode 100644 index 000000000..fc23568f7 --- /dev/null +++ b/src/browser/tests/frames/support/cross_realm_focus.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/browser/tests/frames/support/cross_realm_identity.html b/src/browser/tests/frames/support/cross_realm_identity.html new file mode 100644 index 000000000..857b90244 --- /dev/null +++ b/src/browser/tests/frames/support/cross_realm_identity.html @@ -0,0 +1,13 @@ + + + +quote+ + + diff --git a/src/browser/tests/shadowroot/form_association_scoping.html b/src/browser/tests/shadowroot/form_association_scoping.html new file mode 100644 index 000000000..5bf962e45 --- /dev/null +++ b/src/browser/tests/shadowroot/form_association_scoping.html @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + diff --git a/src/browser/webapi/Document.zig b/src/browser/webapi/Document.zig index 4801fc783..7373e825b 100644 --- a/src/browser/webapi/Document.zig +++ b/src/browser/webapi/Document.zig @@ -132,9 +132,10 @@ pub fn getLocation(self: *const Document) ?*Location { return doc_frame.window._location; } -pub fn setLocation(self: *Document, url: [:0]const u8, frame: *Frame) !void { +pub fn setLocation(self: *Document, url: [:0]const u8) !void { if (self._type != .html) return; - return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = self._frame }); + const frame = self._frame orelse return; + return frame.scheduleNavigation(url, .{ .reason = .script, .kind = .{ .push = null } }, .{ .script = frame }); } pub fn getContentType(self: *const Document) []const u8 { @@ -145,8 +146,8 @@ pub fn getContentType(self: *const Document) []const u8 { }; } -pub fn getDomain(_: *const Document, frame: *const Frame) []const u8 { - return URL.getHostname(frame.url); +pub fn getDomain(self: *const Document, frame: *const Frame) []const u8 { + return URL.getHostname((self._frame orelse frame).url); } const CreateElementOptions = struct { @@ -1227,7 +1228,8 @@ pub const JsApi = struct { pub const hidden = bridge.property(false, .{ .template = false, .readonly = true }); pub const visibilityState = bridge.property("visible", .{ .template = false, .readonly = true }); pub const defaultView = bridge.accessor(struct { - fn defaultView(_: *const Document, frame: *Frame) *@import("Window.zig") { + fn defaultView(self: *const Document) ?*@import("Window.zig") { + const frame = self._frame orelse return null; return frame.window; } }.defaultView, null, .{}); diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index c7fe511d6..c02c7a621 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -990,8 +990,9 @@ pub fn focus(self: *Element, frame: *Frame) !void { const FocusEvent = @import("event/FocusEvent.zig"); const new_target = self.asEventTarget(); - const old_active = frame.document._active_element; - frame.document._active_element = self; + const doc = self.asNode().ownerDocument(frame) orelse frame.document; + const old_active = doc._active_element; + doc._active_element = self; if (old_active) |old| { if (old == self) { @@ -1021,9 +1022,10 @@ pub fn focus(self: *Element, frame: *Frame) !void { } pub fn blur(self: *Element, frame: *Frame) !void { - if (frame.document._active_element != self) return; + const doc = self.asNode().ownerDocument(frame) orelse frame.document; + if (doc._active_element != self) return; - frame.document._active_element = null; + doc._active_element = null; const FocusEvent = @import("event/FocusEvent.zig"); const old_target = self.asEventTarget(); diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index 8479c3d7b..a75e81c50 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -470,9 +470,7 @@ pub fn isConnected(self: *const Node) bool { const GetRootNodeOpts = struct { composed: bool = false, }; -pub fn getRootNode(self: *Node, opts_: ?GetRootNodeOpts) *Node { - const opts = opts_ orelse GetRootNodeOpts{}; - +pub fn getRootNode(self: *Node, opts: GetRootNodeOpts) *Node { var root = self; while (root._parent) |parent| { root = parent; @@ -1229,16 +1227,29 @@ pub const JsApi = struct { pub const normalize = bridge.function(Node.normalize, .{ .ce_reactions = true }); pub const cloneNode = bridge.function(Node.cloneNode, .{ .dom_exception = true, .ce_reactions = true }); pub const compareDocumentPosition = bridge.function(Node.compareDocumentPosition, .{}); - pub const getRootNode = bridge.function(Node.getRootNode, .{}); + pub const getRootNode = bridge.function(_getRootNode, .{}); + // The `options` argument is optional in JS; default it before calling the + // (non-optional) Node.getRootNode. + fn _getRootNode(self: *Node, opts: ?GetRootNodeOpts) *Node { + return self.getRootNode(opts orelse .{}); + } pub const isEqualNode = bridge.function(Node.isEqualNode, .{}); pub const lookupNamespaceURI = bridge.function(Node.lookupNamespaceURI, .{}); pub const lookupPrefix = bridge.function(Node.lookupPrefix, .{}); pub const isDefaultNamespace = bridge.function(Node.isDefaultNamespace, .{}); - fn _baseURI(_: *Node, frame: *const Frame) []const u8 { - return frame.base(); - } pub const baseURI = bridge.accessor(_baseURI, null, .{}); + fn _baseURI(self: *Node, frame: *const Frame) []const u8 { + const doc = if (self._type == .document) + self._type.document + else + self.ownerDocument(frame) orelse return frame.base(); + + if (doc._frame) |doc_frame| { + return doc_frame.base(); + } + return doc.getURL(frame); + } }; pub const Build = struct { diff --git a/src/browser/webapi/Range.zig b/src/browser/webapi/Range.zig index e7c768735..2cf76477c 100644 --- a/src/browser/webapi/Range.zig +++ b/src/browser/webapi/Range.zig @@ -60,8 +60,8 @@ pub fn setStart(self: *Range, node: *Node, offset: u32) !void { self._proto._start_offset = offset; // If start is now after end, or nodes are in different trees, collapse to start - const end_root = self._proto._end_container.getRootNode(null); - const start_root = node.getRootNode(null); + const end_root = self._proto._end_container.getRootNode(.{}); + const start_root = node.getRootNode(.{}); if (end_root != start_root or self._proto.isStartAfterEnd()) { self._proto._end_container = self._proto._start_container; self._proto._end_offset = self._proto._start_offset; @@ -82,8 +82,8 @@ pub fn setEnd(self: *Range, node: *Node, offset: u32) !void { self._proto._end_offset = offset; // If end is now before start, or nodes are in different trees, collapse to end - const start_root = self._proto._start_container.getRootNode(null); - const end_root = node.getRootNode(null); + const start_root = self._proto._start_container.getRootNode(.{}); + const end_root = node.getRootNode(.{}); if (start_root != end_root or self._proto.isStartAfterEnd()) { self._proto._start_container = self._proto._end_container; self._proto._start_offset = self._proto._end_offset; @@ -154,8 +154,8 @@ pub fn compareBoundaryPoints(self: *const Range, how_raw: i32, source_range: *co } // If the two ranges' root is different, throw WrongDocumentError - const this_root = self._proto._start_container.getRootNode(null); - const source_root = source_range._proto._start_container.getRootNode(null); + const this_root = self._proto._start_container.getRootNode(.{}); + const source_root = source_range._proto._start_container.getRootNode(.{}); if (this_root != source_root) { return error.WrongDocument; } @@ -198,8 +198,8 @@ pub fn compareBoundaryPoints(self: *const Range, how_raw: i32, source_range: *co pub fn comparePoint(self: *const Range, node: *Node, offset: u32) !i16 { // Check if node is in a different tree than the range - const node_root = node.getRootNode(null); - const start_root = self._proto._start_container.getRootNode(null); + const node_root = node.getRootNode(.{}); + const start_root = self._proto._start_container.getRootNode(.{}); if (node_root != start_root) { return error.WrongDocument; } @@ -236,8 +236,8 @@ pub fn comparePoint(self: *const Range, node: *Node, offset: u32) !i16 { pub fn isPointInRange(self: *const Range, node: *Node, offset: u32) !bool { // If node's root is different from the context object's root, return false - const node_root = node.getRootNode(null); - const start_root = self._proto._start_container.getRootNode(null); + const node_root = node.getRootNode(.{}); + const start_root = self._proto._start_container.getRootNode(.{}); if (node_root != start_root) { return false; } @@ -275,8 +275,8 @@ pub fn isPointInRange(self: *const Range, node: *Node, offset: u32) !bool { pub fn intersectsNode(self: *const Range, node: *Node) bool { // If node's root is different from the context object's root, return false - const node_root = node.getRootNode(null); - const start_root = self._proto._start_container.getRootNode(null); + const node_root = node.getRootNode(.{}); + const start_root = self._proto._start_container.getRootNode(.{}); if (node_root != start_root) { return false; } diff --git a/src/browser/webapi/collections/HTMLAllCollection.zig b/src/browser/webapi/collections/HTMLAllCollection.zig index 48e2952f5..c8c930349 100644 --- a/src/browser/webapi/collections/HTMLAllCollection.zig +++ b/src/browser/webapi/collections/HTMLAllCollection.zig @@ -100,8 +100,7 @@ pub fn getAtIndex(self: *HTMLAllCollection, index: usize, frame: *const Frame) ? } pub fn getByName(self: *HTMLAllCollection, name: []const u8, frame: *Frame) ?*Element { - // First, try fast ID lookup using the document's element map - if (frame.document._elements_by_id.get(name)) |el| { + if (frame.getElementByIdFromNode(self._tw._root, name)) |el| { return el; } diff --git a/src/browser/webapi/collections/node_live.zig b/src/browser/webapi/collections/node_live.zig index 73b19714a..ab96ed661 100644 --- a/src/browser/webapi/collections/node_live.zig +++ b/src/browser/webapi/collections/node_live.zig @@ -179,7 +179,7 @@ pub fn NodeLive(comptime mode: Mode) type { } pub fn getByName(self: *Self, name: []const u8, frame: *Frame) ?*Element { - if (frame.document.getElementById(name, frame)) |element| { + if (frame.getElementByIdFromNode(self._tw._root, name)) |element| { const node = element.asNode(); if (self._tw.contains(node) and self.matches(node)) { return element; diff --git a/src/browser/webapi/element/html/Base.zig b/src/browser/webapi/element/html/Base.zig index 9f86df1ca..ee47c0453 100644 --- a/src/browser/webapi/element/html/Base.zig +++ b/src/browser/webapi/element/html/Base.zig @@ -31,7 +31,8 @@ pub fn getHref(self: *Base, frame: *Frame) ![]const u8 { if (href.len == 0) { return ""; } - return URL.resolve(frame.call_arena, frame.url, href, .{}); + const owner = element.asConstNode().ownerFrame(frame); + return URL.resolve(frame.call_arena, owner.url, href, .{}); } pub fn setHref(self: *Base, value: []const u8, frame: *Frame) !void { diff --git a/src/browser/webapi/element/html/Button.zig b/src/browser/webapi/element/html/Button.zig index 476a24363..d6fcab0c9 100644 --- a/src/browser/webapi/element/html/Button.zig +++ b/src/browser/webapi/element/html/Button.zig @@ -100,7 +100,9 @@ pub fn getForm(self: *Button, frame: *Frame) ?*Form { // If form attribute exists, ONLY use that (even if it references nothing) if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { - if (frame.document.getElementById(form_id, frame)) |form_element| { + // form= resolves in the control's own tree (shadow root or document), + // not the calling realm's. getElementByIdFromNode walks to that root. + if (frame.getElementByIdFromNode(element.asNode(), form_id)) |form_element| { return form_element.is(Form); } // form attribute present but invalid - no form owner @@ -135,9 +137,10 @@ pub fn getLabels(self: *Button, frame: *Frame) !js.Array { pub fn getFormAction(self: *Button, frame: *Frame) ![]const u8 { const element = self.asElement(); - const action = element.getAttributeSafe(comptime .wrap("formaction")) orelse return frame.url; + const owner_url = element.asNode().ownerFrame(frame).url; + const action = element.getAttributeSafe(comptime .wrap("formaction")) orelse return owner_url; if (action.len == 0) { - return frame.url; + return owner_url; } return element.asNode().resolveURL(action, frame, .{}); } diff --git a/src/browser/webapi/element/html/Form.zig b/src/browser/webapi/element/html/Form.zig index bd634b6fd..c9999b215 100644 --- a/src/browser/webapi/element/html/Form.zig +++ b/src/browser/webapi/element/html/Form.zig @@ -106,7 +106,7 @@ pub fn getElements(self: *Form, frame: *Frame) !*collections.HTMLFormControlsCol pub fn iterator(self: *Form, frame: *Frame) collections.NodeLive(.form) { const form_id = self.asElement().getAttributeSafe(comptime .wrap("id")); const root = if (form_id != null) - self.asNode().getRootNode(null) // Has ID: walk entire document to find form=ID controls + self.asNode().getRootNode(.{}) // Has ID: walk entire document to find form=ID controls else self.asNode(); // No ID: walk only form subtree (no external controls possible) @@ -115,9 +115,10 @@ pub fn iterator(self: *Form, frame: *Frame) collections.NodeLive(.form) { pub fn getAction(self: *Form, frame: *Frame) ![]const u8 { const element = self.asElement(); - const action = element.getAttributeSafe(comptime .wrap("action")) orelse return frame.url; + const owner_url = element.asNode().ownerFrame(frame).url; + const action = element.getAttributeSafe(comptime .wrap("action")) orelse return owner_url; if (action.len == 0) { - return frame.url; + return owner_url; } return element.asNode().resolveURL(action, frame, .{}); } diff --git a/src/browser/webapi/element/html/Input.zig b/src/browser/webapi/element/html/Input.zig index d624254d5..cca413c20 100644 --- a/src/browser/webapi/element/html/Input.zig +++ b/src/browser/webapi/element/html/Input.zig @@ -305,7 +305,7 @@ pub fn getValueForJS(self: *const Input, frame: *Frame) ![]const u8 { return try std.fmt.allocPrint(frame.call_arena, "C:\\fakepath\\{s}", .{fl._files[0]._name}); } -pub fn getValidationMessage(self: *const Input, frame: *Frame) []const u8 { +pub fn getValidationMessage(self: *Input, frame: *Frame) []const u8 { if (!self.getWillValidate()) return ""; if (self._custom_validity) |msg| return msg; if (self.suffersValueMissing(frame)) return "Please fill out this field."; @@ -349,7 +349,7 @@ pub fn hasCustomValidity(self: *const Input) bool { return self._custom_validity != null; } -pub fn suffersValueMissing(self: *const Input, frame: *Frame) bool { +pub fn suffersValueMissing(self: *Input, frame: *Frame) bool { if (!self.getWillValidate()) return false; if (!self.getRequired()) return false; return switch (self._input_type) { @@ -462,7 +462,7 @@ fn numericRangeBreach(self: *const Input, comptime kind: enum { underflow, overf }; } -fn radioGroupHasChecked(self: *const Input, frame: *Frame) bool { +fn radioGroupHasChecked(self: *Input, frame: *Frame) bool { if (self._checked) return true; var iter = self.radioGroupIterator() orelse return false; const my_form = self.getForm(frame); @@ -501,14 +501,14 @@ fn radioGroupIterator(self: *const Input) ?RadioGroupIterator { const element = self.asConstElement(); const name = element.getAttributeSafe(comptime .wrap("name")) orelse return null; if (name.len == 0) return null; - const root = @constCast(element.asConstNode()).getRootNode(null); + const root = @constCast(element.asConstNode()).getRootNode(.{}); return .{ .walker = TreeWalker.Full.init(root, .{}), .name = name, }; } -fn sameFormOwner(self_form: ?*const Form, other: *const Input, frame: *Frame) bool { +fn sameFormOwner(self_form: ?*Form, other: *Input, frame: *Frame) bool { const other_form = other.getForm(frame); // Check if same form context @@ -659,8 +659,7 @@ pub fn setSize(self: *Input, size: i32, frame: *Frame) !void { pub fn getSrc(self: *const Input, frame: *Frame) ![]const u8 { const src = self.asConstElement().getAttributeSafe(comptime .wrap("src")) orelse return ""; - // If attribute is explicitly set (even if empty), resolve it against the base URL - return @import("../../URL.zig").resolve(frame.call_arena, frame.base(), src, .{}); + return self.asConstElement().asConstNode().resolveURL(src, frame, .{}); } pub fn setSrc(self: *Input, src: []const u8, frame: *Frame) !void { @@ -890,12 +889,16 @@ pub fn getLabels(self: *Input, frame: *Frame) !js.Array { return @import("Label.zig").getControlLabels(self.asElement(), frame); } -pub fn getForm(self: *const Input, frame: *Frame) ?*Form { - const element = self.asConstElement(); +pub fn getForm(self: *Input, frame: *Frame) ?*Form { + const element = self.asElement(); // If form attribute exists, ONLY use that (even if it references nothing) if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { - if (frame.document.getElementById(form_id, frame)) |form_element| { + // form= resolves in the control's own tree (shadow root or document), + // not the calling realm's. @constCast: getElementByIdFromNode wants a + // mutable *Node but doesn't mutate it (same idiom as radioGroupIterator); + // keeping getForm const avoids a wide validity-path const cascade. + if (frame.getElementByIdFromNode(element.asNode(), form_id)) |form_element| { return form_element.is(Form); } // form attribute present but invalid - no form owner @@ -920,9 +923,10 @@ pub fn getForm(self: *const Input, frame: *Frame) ?*Form { pub fn getFormAction(self: *Input, frame: *Frame) ![]const u8 { const element = self.asElement(); - const action = element.getAttributeSafe(comptime .wrap("formaction")) orelse return frame.url; + const owner_url = element.asNode().ownerFrame(frame).url; + const action = element.getAttributeSafe(comptime .wrap("formaction")) orelse return owner_url; if (action.len == 0) { - return frame.url; + return owner_url; } return element.asNode().resolveURL(action, frame, .{}); } diff --git a/src/browser/webapi/element/html/Label.zig b/src/browser/webapi/element/html/Label.zig index 0cbd3509b..5b31aa01e 100644 --- a/src/browser/webapi/element/html/Label.zig +++ b/src/browser/webapi/element/html/Label.zig @@ -27,7 +27,7 @@ pub fn setHtmlFor(self: *Label, value: []const u8, frame: *Frame) !void { pub fn getControl(self: *Label, frame: *Frame) ?*Element { if (self.asElement().getAttributeSafe(comptime .wrap("for"))) |id| { - const el = frame.document.getElementById(id, frame) orelse return null; + const el = frame.getElementByIdFromNode(self.asElement().asNode(), id) orelse return null; if (!isLabelable(el)) { return null; } diff --git a/src/browser/webapi/element/html/Quote.zig b/src/browser/webapi/element/html/Quote.zig index 8fc6726e8..47befe495 100644 --- a/src/browser/webapi/element/html/Quote.zig +++ b/src/browser/webapi/element/html/Quote.zig @@ -24,8 +24,7 @@ pub fn asNode(self: *Quote) *Node { pub fn getCite(self: *Quote, frame: *Frame) ![]const u8 { const attr = self.asElement().getAttributeSafe(comptime .wrap("cite")) orelse return ""; if (attr.len == 0) return ""; - const URL = @import("../../URL.zig"); - return URL.resolve(frame.call_arena, frame.base(), attr, .{}); + return self.asNode().resolveURL(attr, frame, .{}); } pub fn setCite(self: *Quote, value: []const u8, frame: *Frame) !void { diff --git a/src/browser/webapi/element/html/Select.zig b/src/browser/webapi/element/html/Select.zig index 262a4d1c9..48dcb3189 100644 --- a/src/browser/webapi/element/html/Select.zig +++ b/src/browser/webapi/element/html/Select.zig @@ -231,7 +231,7 @@ pub fn getForm(self: *Select, frame: *Frame) ?*Form { // If form attribute exists, ONLY use that (even if it references nothing) if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { - if (frame.document.getElementById(form_id, frame)) |form_element| { + if (frame.getElementByIdFromNode(element.asNode(), form_id)) |form_element| { return form_element.is(Form); } // form attribute present but invalid - no form owner diff --git a/src/browser/webapi/element/html/Style.zig b/src/browser/webapi/element/html/Style.zig index 48388a203..811c8d691 100644 --- a/src/browser/webapi/element/html/Style.zig +++ b/src/browser/webapi/element/html/Style.zig @@ -95,7 +95,8 @@ pub fn getSheet(self: *Style, frame: *Frame) !?*CSSStyleSheet { const sheet = try CSSStyleSheet.initWithOwner(self.asElement(), frame); self._sheet = sheet; - const sheets = try frame.document.getStyleSheets(frame); + const owner = self.asNode().ownerDocument(frame) orelse frame.document; + const sheets = try owner.getStyleSheets(frame); try sheets.add(sheet, frame); return sheet; diff --git a/src/browser/webapi/element/html/TextArea.zig b/src/browser/webapi/element/html/TextArea.zig index 1524168df..db43bf927 100644 --- a/src/browser/webapi/element/html/TextArea.zig +++ b/src/browser/webapi/element/html/TextArea.zig @@ -292,7 +292,7 @@ pub fn getForm(self: *TextArea, frame: *Frame) ?*Form { // If form attribute exists, ONLY use that (even if it references nothing) if (element.getAttributeSafe(comptime .wrap("form"))) |form_id| { - if (frame.document.getElementById(form_id, frame)) |form_element| { + if (frame.getElementByIdFromNode(element.asNode(), form_id)) |form_element| { return form_element.is(Form); } // form attribute present but invalid - no form owner diff --git a/src/browser/webapi/element/popover.zig b/src/browser/webapi/element/popover.zig index e7a97183e..68c490bc4 100644 --- a/src/browser/webapi/element/popover.zig +++ b/src/browser/webapi/element/popover.zig @@ -193,7 +193,7 @@ pub fn removeFromOpen(el: *Element, frame: *Frame) void { // the explicitly IDL-set element or the `popovertarget` IDREF content attribute. pub fn invokerTarget(invoker: *Node, explicit: ?*Element, frame: *Frame) ?*Element { if (explicit) |target| { - if (target.asNode().getRootNode(null) == invoker.getRootNode(null)) { + if (target.asNode().getRootNode(.{}) == invoker.getRootNode(.{})) { // The invoker and target must share the same root return target; } @@ -201,7 +201,7 @@ pub fn invokerTarget(invoker: *Node, explicit: ?*Element, frame: *Frame) ?*Eleme } const el = invoker.is(Element) orelse return null; const id = el.getAttributeSafe(.wrap("popovertarget")) orelse return null; - return frame.document.getElementById(id, frame); + return frame.getElementByIdFromNode(el.asNode(), id); } pub fn runInvokerActivation(invoker: *HtmlElement, explicit: ?*Element, frame: *Frame) !void { diff --git a/src/browser/webapi/selector/List.zig b/src/browser/webapi/selector/List.zig index a0cd83ec1..e5cbfd5e6 100644 --- a/src/browser/webapi/selector/List.zig +++ b/src/browser/webapi/selector/List.zig @@ -601,11 +601,13 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N .hover => return false, .active => return false, .focus => { - const active = frame.document._active_element orelse return false; + const doc = node.ownerDocument(frame) orelse return false; + const active = doc._active_element orelse return false; return active == el; }, .focus_within => { - const active = frame.document._active_element orelse return false; + const doc = node.ownerDocument(frame) orelse return false; + const active = doc._active_element orelse return false; return node.contains(active.asNode()); }, .focus_visible => return false, @@ -619,7 +621,8 @@ fn matchesPseudoClass(el: *Node.Element, pseudo: Selector.PseudoClass, scope: *N }, .target => { const element_id = el.getAttributeSafe(comptime .wrap("id")) orelse return false; - const location = frame.document.getLocation() orelse return false; + const doc = node.ownerDocument(frame) orelse return false; + const location = doc.getLocation() orelse return false; const hash = location.getHash(); if (hash.len <= 1) return false; return std.mem.eql(u8, element_id, hash[1..]);