Skip to content

Commit 4c1b4a2

Browse files
committed
Detached node can't have focus.
Refactor isNodeAttached because of the "law of three."
1 parent c52edd0 commit 4c1b4a2

File tree

3 files changed

+19
-5
lines changed

3 files changed

+19
-5
lines changed

src/browser/dom/element.zig

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,7 @@ pub const Element = struct {
368368
// Returns a 0 DOMRect object if the element is eventually detached from the main window
369369
pub fn _getBoundingClientRect(self: *parser.Element, page: *Page) !DOMRect {
370370
// 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.
371-
const root = try parser.nodeGetRootNode(parser.elementToNode(self));
372-
if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) {
371+
if (!try page.isNodeAttached(parser.elementToNode(self))) {
373372
return DOMRect{ .x = 0, .y = 0, .width = 0, .height = 0 };
374373
}
375374
return page.renderer.getRect(self);
@@ -379,8 +378,7 @@ pub const Element = struct {
379378
// We do not render so it only always return the element's bounding rect.
380379
// Returns an empty array if the element is eventually detached from the main window
381380
pub fn _getClientRects(self: *parser.Element, page: *Page) ![]DOMRect {
382-
const root = try parser.nodeGetRootNode(parser.elementToNode(self));
383-
if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) {
381+
if (!try page.isNodeAttached(parser.elementToNode(self))) {
384382
return &.{};
385383
}
386384
const heap_ptr = try page.call_arena.create(DOMRect);

src/browser/html/elements.zig

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,11 @@ pub const HTMLElement = struct {
149149
_ = try parser.elementDispatchEvent(@ptrCast(e), @ptrCast(event));
150150
}
151151

152-
pub fn _focus(e: *parser.ElementHTML, page: *Page) void {
152+
pub fn _focus(e: *parser.ElementHTML, page: *Page) !void {
153+
if (!try page.isNodeAttached(@ptrCast(e))) {
154+
return;
155+
}
156+
153157
// TODO: some elements can't be focused, like if they're disabled
154158
// but there doesn't seem to be a generic way to check this. For example
155159
// we could look for the "disabled" attribute, but that's only meaningful
@@ -1182,4 +1186,11 @@ test "Browser.HTML.Element" {
11821186
.{ "lyric.src = 15", "15" },
11831187
.{ "lyric.src", "15" },
11841188
}, .{});
1189+
1190+
// detached node cannot be focused
1191+
try runner.testCases(&.{
1192+
.{ "const focused = document.activeElement", null },
1193+
.{ "document.createElement('a').focus()", null },
1194+
.{ "document.activeElement === focused", "true" },
1195+
}, .{});
11851196
}

src/browser/page.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,11 @@ pub const Page = struct {
659659
return null;
660660
}
661661

662+
pub fn isNodeAttached(self: *const Page, node: *parser.Node) !bool {
663+
const root = parser.documentToNode(parser.documentHTMLToDocument(self.window.document));
664+
return root == try parser.nodeGetRootNode(node);
665+
}
666+
662667
fn elementSubmitForm(self: *Page, element: *parser.Element) !void {
663668
const form = (try self.formForElement(element)) orelse return;
664669
return self.submitForm(@ptrCast(form), @ptrCast(element));

0 commit comments

Comments
 (0)