mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-28 22:53:28 +00:00
Detached node can't have focus.
Refactor isNodeAttached because of the "law of three."
This commit is contained in:
@@ -42,6 +42,8 @@ pub const Document = struct {
|
|||||||
pub const prototype = *Node;
|
pub const prototype = *Node;
|
||||||
pub const subtype = .node;
|
pub const subtype = .node;
|
||||||
|
|
||||||
|
active_element: ?*parser.Element = null,
|
||||||
|
|
||||||
pub fn constructor(page: *const Page) !*parser.DocumentHTML {
|
pub fn constructor(page: *const Page) !*parser.DocumentHTML {
|
||||||
const doc = try parser.documentCreateDocument(
|
const doc = try parser.documentCreateDocument(
|
||||||
try parser.documentHTMLGetTitle(page.window.document),
|
try parser.documentHTMLGetTitle(page.window.document),
|
||||||
@@ -243,9 +245,17 @@ pub const Document = struct {
|
|||||||
return try TreeWalker.init(root, what_to_show, filter);
|
return try TreeWalker.init(root, what_to_show, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_activeElement(_: *parser.Document, page: *const Page) !?ElementUnion {
|
pub fn get_activeElement(doc: *parser.Document, page: *Page) !?ElementUnion {
|
||||||
const el = (try page.activeElement()) orelse return null;
|
const self = try page.getOrCreateNodeWrapper(Document, @ptrCast(doc));
|
||||||
return try Element.toInterface(el);
|
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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -368,8 +368,7 @@ pub const Element = struct {
|
|||||||
// Returns a 0 DOMRect object if the element is eventually detached from the main window
|
// Returns a 0 DOMRect object if the element is eventually detached from the main window
|
||||||
pub fn _getBoundingClientRect(self: *parser.Element, page: *Page) !DOMRect {
|
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.
|
// 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 (!try page.isNodeAttached(parser.elementToNode(self))) {
|
||||||
if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) {
|
|
||||||
return DOMRect{ .x = 0, .y = 0, .width = 0, .height = 0 };
|
return DOMRect{ .x = 0, .y = 0, .width = 0, .height = 0 };
|
||||||
}
|
}
|
||||||
return page.renderer.getRect(self);
|
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.
|
// 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
|
// Returns an empty array if the element is eventually detached from the main window
|
||||||
pub fn _getClientRects(self: *parser.Element, page: *Page) ![]DOMRect {
|
pub fn _getClientRects(self: *parser.Element, page: *Page) ![]DOMRect {
|
||||||
const root = try parser.nodeGetRootNode(parser.elementToNode(self));
|
if (!try page.isNodeAttached(parser.elementToNode(self))) {
|
||||||
if (root != parser.documentToNode(parser.documentHTMLToDocument(page.window.document))) {
|
|
||||||
return &.{};
|
return &.{};
|
||||||
}
|
}
|
||||||
const heap_ptr = try page.call_arena.create(DOMRect);
|
const heap_ptr = try page.call_arena.create(DOMRect);
|
||||||
|
|||||||
@@ -149,12 +149,25 @@ pub const HTMLElement = struct {
|
|||||||
_ = try parser.elementDispatchEvent(@ptrCast(e), @ptrCast(event));
|
_ = 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
|
// 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
|
// 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
|
// 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.
|
// 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 = 'about'", null },
|
||||||
.{ "a.href", "https://lightpanda.io/opensource-browser/about" },
|
.{ "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" },
|
||||||
|
}, .{});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,6 @@ pub const Page = struct {
|
|||||||
// indicates intention to navigate to another page on the next loop execution.
|
// indicates intention to navigate to another page on the next loop execution.
|
||||||
delayed_navigation: bool = false,
|
delayed_navigation: bool = false,
|
||||||
|
|
||||||
active_element: ?*parser.Element = null,
|
|
||||||
|
|
||||||
pub fn init(self: *Page, arena: Allocator, session: *Session) !void {
|
pub fn init(self: *Page, arena: Allocator, session: *Session) !void {
|
||||||
const browser = session.browser;
|
const browser = session.browser;
|
||||||
self.* = .{
|
self.* = .{
|
||||||
@@ -645,21 +643,9 @@ pub const Page = struct {
|
|||||||
try self.navigateFromWebAPI(action, opts);
|
try self.navigateFromWebAPI(action, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn activeElement(self: *const Page) !?*parser.Element {
|
pub fn isNodeAttached(self: *const Page, node: *parser.Node) !bool {
|
||||||
if (self.active_element) |ae| {
|
const root = parser.documentToNode(parser.documentHTMLToDocument(self.window.document));
|
||||||
return ae;
|
return root == try parser.nodeGetRootNode(node);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn elementSubmitForm(self: *Page, element: *parser.Element) !void {
|
fn elementSubmitForm(self: *Page, element: *parser.Element) !void {
|
||||||
|
|||||||
Reference in New Issue
Block a user