diff --git a/src/browser/dom/document.zig b/src/browser/dom/document.zig index a4942f75..4d6832e3 100644 --- a/src/browser/dom/document.zig +++ b/src/browser/dom/document.zig @@ -42,6 +42,8 @@ pub const Document = struct { pub const prototype = *Node; pub const subtype = .node; + active_element: ?*parser.Element = null, + pub fn constructor(page: *const Page) !*parser.DocumentHTML { const doc = try parser.documentCreateDocument( try parser.documentHTMLGetTitle(page.window.document), @@ -243,9 +245,17 @@ pub const Document = struct { return try TreeWalker.init(root, what_to_show, filter); } - pub fn get_activeElement(_: *parser.Document, page: *const Page) !?ElementUnion { - const el = (try page.activeElement()) orelse return null; - return try Element.toInterface(el); + pub fn get_activeElement(doc: *parser.Document, page: *Page) !?ElementUnion { + const self = try page.getOrCreateNodeWrapper(Document, @ptrCast(doc)); + if (self.active_element) |ae| { + return try Element.toInterface(ae); + } + + if (try parser.documentHTMLBody(page.window.document)) |body| { + return try Element.toInterface(@ptrCast(body)); + } + + return get_documentElement(doc); } }; diff --git a/src/browser/dom/element.zig b/src/browser/dom/element.zig index f640686d..d63e5d2c 100644 --- a/src/browser/dom/element.zig +++ b/src/browser/dom/element.zig @@ -368,8 +368,7 @@ pub const Element = struct { // Returns a 0 DOMRect object if the element is eventually detached from the main window pub fn _getBoundingClientRect(self: *parser.Element, page: *Page) !DOMRect { // Since we are lazy rendering we need to do this check. We could store the renderer in a viewport such that it could cache these, but it would require tracking changes. - const root = try parser.nodeGetRootNode(parser.elementToNode(self)); - if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) { + if (!try page.isNodeAttached(parser.elementToNode(self))) { return DOMRect{ .x = 0, .y = 0, .width = 0, .height = 0 }; } return page.renderer.getRect(self); @@ -379,8 +378,7 @@ pub const Element = struct { // We do not render so it only always return the element's bounding rect. // Returns an empty array if the element is eventually detached from the main window pub fn _getClientRects(self: *parser.Element, page: *Page) ![]DOMRect { - const root = try parser.nodeGetRootNode(parser.elementToNode(self)); - if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) { + if (!try page.isNodeAttached(parser.elementToNode(self))) { return &.{}; } const heap_ptr = try page.call_arena.create(DOMRect); diff --git a/src/browser/html/elements.zig b/src/browser/html/elements.zig index c5eaa1ef..821203b7 100644 --- a/src/browser/html/elements.zig +++ b/src/browser/html/elements.zig @@ -149,12 +149,25 @@ pub const HTMLElement = struct { _ = try parser.elementDispatchEvent(@ptrCast(e), @ptrCast(event)); } - pub fn _focus(e: *parser.ElementHTML, page: *Page) void { + const FocusOpts = struct { + preventScroll: bool, + focusVisible: bool, + }; + pub fn _focus(e: *parser.ElementHTML, _: ?FocusOpts, page: *Page) !void { + if (!try page.isNodeAttached(@ptrCast(e))) { + return; + } + + const root_node = try parser.nodeGetRootNode(@ptrCast(e)); + + const Document = @import("../dom/document.zig").Document; + const document = try page.getOrCreateNodeWrapper(Document, @ptrCast(root_node)); + // TODO: some elements can't be focused, like if they're disabled // but there doesn't seem to be a generic way to check this. For example // we could look for the "disabled" attribute, but that's only meaningful // on certain types, and libdom's vtable doesn't seem to expose this. - page.active_element = @ptrCast(e); + document.active_element = @ptrCast(e); } }; @@ -1189,4 +1202,11 @@ test "Browser.HTML.Element" { .{ "a.href = 'about'", null }, .{ "a.href", "https://lightpanda.io/opensource-browser/about" }, }, .{}); + + // detached node cannot be focused + try runner.testCases(&.{ + .{ "const focused = document.activeElement", null }, + .{ "document.createElement('a').focus()", null }, + .{ "document.activeElement === focused", "true" }, + }, .{}); } diff --git a/src/browser/page.zig b/src/browser/page.zig index 1ce081fe..47c85209 100644 --- a/src/browser/page.zig +++ b/src/browser/page.zig @@ -95,8 +95,6 @@ pub const Page = struct { // indicates intention to navigate to another page on the next loop execution. delayed_navigation: bool = false, - active_element: ?*parser.Element = null, - pub fn init(self: *Page, arena: Allocator, session: *Session) !void { const browser = session.browser; self.* = .{ @@ -645,21 +643,9 @@ pub const Page = struct { try self.navigateFromWebAPI(action, opts); } - pub fn activeElement(self: *const Page) !?*parser.Element { - if (self.active_element) |ae| { - return ae; - } - - if (try parser.documentHTMLBody(self.window.document)) |body| { - return @ptrCast(body); - } - - const doc = self.window.document; - if (try parser.documentGetDocumentElement(@ptrCast(doc))) |de| { - return @ptrCast(de); - } - - return null; + pub fn isNodeAttached(self: *const Page, node: *parser.Node) !bool { + const root = parser.documentToNode(parser.documentHTMLToDocument(self.window.document)); + return root == try parser.nodeGetRootNode(node); } fn elementSubmitForm(self: *Page, element: *parser.Element) !void {