mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Add various element position properties
clientTop, clientLeft, scrollTop, scrollLeft, scrollHeight, scrollWidth, offsetTop, offsetLeft, offsetWidth, offsetHeight. These are all dummy implementation that hook, as much as possible, into what layout information we have. Explicitly set scroll information is stored on the page.
This commit is contained in:
@@ -106,6 +106,7 @@ _element_rel_lists: Element.RelListLookup = .empty,
|
||||
_element_shadow_roots: Element.ShadowRootLookup = .empty,
|
||||
_node_owner_documents: Node.OwnerDocumentLookup = .empty,
|
||||
_element_assigned_slots: Element.AssignedSlotLookup = .empty,
|
||||
_element_scroll_positions: Element.ScrollPositionLookup = .empty,
|
||||
|
||||
/// Lazily-created inline event listeners (or listeners provided as attributes).
|
||||
/// Avoids bloating all elements with extra function fields for rare usage.
|
||||
|
||||
116
src/browser/tests/element/position.html
Normal file
116
src/browser/tests/element/position.html
Normal file
@@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
|
||||
<div id="test1">Test Element</div>
|
||||
<div id="test2">Another Element</div>
|
||||
|
||||
<script id="clientDimensions">
|
||||
{
|
||||
const test1 = $('#test1');
|
||||
|
||||
// clientWidth/Height - default is 5px in dummy layout
|
||||
testing.expectEqual('number', typeof test1.clientWidth);
|
||||
testing.expectEqual('number', typeof test1.clientHeight);
|
||||
testing.expectTrue(test1.clientWidth >= 0);
|
||||
testing.expectTrue(test1.clientHeight >= 0);
|
||||
|
||||
// clientTop/Left should be 0 (no borders in dummy layout)
|
||||
testing.expectEqual(0, test1.clientTop);
|
||||
testing.expectEqual(0, test1.clientLeft);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="scrollDimensions">
|
||||
{
|
||||
const test1 = $('#test1');
|
||||
|
||||
// In dummy layout, scroll dimensions equal client dimensions (no overflow)
|
||||
testing.expectEqual(test1.clientWidth, test1.scrollWidth);
|
||||
testing.expectEqual(test1.clientHeight, test1.scrollHeight);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="scrollPosition">
|
||||
{
|
||||
const test1 = $('#test1');
|
||||
|
||||
// Initial scroll position should be 0
|
||||
testing.expectEqual(0, test1.scrollTop);
|
||||
testing.expectEqual(0, test1.scrollLeft);
|
||||
|
||||
// Setting scroll position
|
||||
test1.scrollTop = 50;
|
||||
testing.expectEqual(50, test1.scrollTop);
|
||||
|
||||
test1.scrollLeft = 25;
|
||||
testing.expectEqual(25, test1.scrollLeft);
|
||||
|
||||
// Negative values should be clamped to 0
|
||||
test1.scrollTop = -10;
|
||||
testing.expectEqual(0, test1.scrollTop);
|
||||
|
||||
test1.scrollLeft = -5;
|
||||
testing.expectEqual(0, test1.scrollLeft);
|
||||
|
||||
// Each element has independent scroll position
|
||||
const test2 = $('#test2');
|
||||
testing.expectEqual(0, test2.scrollTop);
|
||||
testing.expectEqual(0, test2.scrollLeft);
|
||||
|
||||
test2.scrollTop = 100;
|
||||
testing.expectEqual(100, test2.scrollTop);
|
||||
testing.expectEqual(0, test1.scrollTop); // test1 should still be 0
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="offsetDimensions">
|
||||
{
|
||||
const test1 = $('#test1');
|
||||
|
||||
// offsetWidth/Height should be numbers
|
||||
testing.expectEqual('number', typeof test1.offsetWidth);
|
||||
testing.expectEqual('number', typeof test1.offsetHeight);
|
||||
testing.expectTrue(test1.offsetWidth >= 0);
|
||||
testing.expectTrue(test1.offsetHeight >= 0);
|
||||
|
||||
// Should equal client dimensions
|
||||
testing.expectEqual(test1.clientWidth, test1.offsetWidth);
|
||||
testing.expectEqual(test1.clientHeight, test1.offsetHeight);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="offsetPosition">
|
||||
{
|
||||
const test1 = $('#test1');
|
||||
const test2 = $('#test2');
|
||||
|
||||
// offsetTop/Left should be calculated from tree position
|
||||
// These values are based on the heuristic layout engine
|
||||
const top1 = test1.offsetTop;
|
||||
const left1 = test1.offsetLeft;
|
||||
const top2 = test2.offsetTop;
|
||||
const left2 = test2.offsetLeft;
|
||||
|
||||
// Position values should be numbers
|
||||
testing.expectEqual('number', typeof top1);
|
||||
testing.expectEqual('number', typeof left1);
|
||||
testing.expectEqual('number', typeof top2);
|
||||
testing.expectEqual('number', typeof left2);
|
||||
|
||||
// Siblings should have different positions (either different x or y)
|
||||
testing.expectTrue(top1 !== top2 || left1 !== left2);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script id="offsetVsBounding">
|
||||
{
|
||||
const test1 = $('#test1');
|
||||
|
||||
// offsetTop/Left should match getBoundingClientRect
|
||||
const rect = test1.getBoundingClientRect();
|
||||
testing.expectEqual(rect.y, test1.offsetTop);
|
||||
testing.expectEqual(rect.x, test1.offsetLeft);
|
||||
testing.expectEqual(rect.width, test1.offsetWidth);
|
||||
testing.expectEqual(rect.height, test1.offsetHeight);
|
||||
}
|
||||
</script>
|
||||
@@ -49,6 +49,12 @@ pub const RelListLookup = std.AutoHashMapUnmanaged(*Element, *collections.DOMTok
|
||||
pub const ShadowRootLookup = std.AutoHashMapUnmanaged(*Element, *ShadowRoot);
|
||||
pub const AssignedSlotLookup = std.AutoHashMapUnmanaged(*Element, *Html.Slot);
|
||||
|
||||
pub const ScrollPosition = struct {
|
||||
x: u32 = 0,
|
||||
y: u32 = 0,
|
||||
};
|
||||
pub const ScrollPositionLookup = std.AutoHashMapUnmanaged(*Element, ScrollPosition);
|
||||
|
||||
pub const Namespace = enum(u8) {
|
||||
html,
|
||||
svg,
|
||||
@@ -1027,6 +1033,82 @@ pub fn getClientRects(self: *Element, page: *Page) ![]DOMRect {
|
||||
return ptr[0..1];
|
||||
}
|
||||
|
||||
pub fn getScrollTop(self: *Element, page: *Page) u32 {
|
||||
const pos = page._element_scroll_positions.get(self) orelse return 0;
|
||||
return pos.y;
|
||||
}
|
||||
|
||||
pub fn setScrollTop(self: *Element, value: i32, page: *Page) !void {
|
||||
const gop = try page._element_scroll_positions.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{};
|
||||
}
|
||||
gop.value_ptr.y = @intCast(@max(0, value));
|
||||
}
|
||||
|
||||
pub fn getScrollLeft(self: *Element, page: *Page) u32 {
|
||||
const pos = page._element_scroll_positions.get(self) orelse return 0;
|
||||
return pos.x;
|
||||
}
|
||||
|
||||
pub fn setScrollLeft(self: *Element, value: i32, page: *Page) !void {
|
||||
const gop = try page._element_scroll_positions.getOrPut(page.arena, self);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{};
|
||||
}
|
||||
gop.value_ptr.x = @intCast(@max(0, value));
|
||||
}
|
||||
|
||||
pub fn getScrollHeight(self: *Element, page: *Page) !f64 {
|
||||
// In our dummy layout engine, content doesn't overflow
|
||||
return self.getClientHeight(page);
|
||||
}
|
||||
|
||||
pub fn getScrollWidth(self: *Element, page: *Page) !f64 {
|
||||
// In our dummy layout engine, content doesn't overflow
|
||||
return self.getClientWidth(page);
|
||||
}
|
||||
|
||||
pub fn getOffsetHeight(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
const dims = try self.getElementDimensions(page);
|
||||
return dims.height;
|
||||
}
|
||||
|
||||
pub fn getOffsetWidth(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
const dims = try self.getElementDimensions(page);
|
||||
return dims.width;
|
||||
}
|
||||
|
||||
pub fn getOffsetTop(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
return calculateDocumentPosition(self.asNode());
|
||||
}
|
||||
|
||||
pub fn getOffsetLeft(self: *Element, page: *Page) !f64 {
|
||||
if (!try self.checkVisibility(page)) {
|
||||
return 0.0;
|
||||
}
|
||||
return calculateSiblingPosition(self.asNode());
|
||||
}
|
||||
|
||||
pub fn getClientTop(_: *Element) f64 {
|
||||
// Border width - in our dummy layout, we don't apply borders to layout
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
pub fn getClientLeft(_: *Element) f64 {
|
||||
// Border width - in our dummy layout, we don't apply borders to layout
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Calculates document position by counting all nodes that appear before this one
|
||||
// in tree order, but only traversing the "left side" of the tree.
|
||||
//
|
||||
@@ -1502,6 +1584,16 @@ pub const JsApi = struct {
|
||||
pub const checkVisibility = bridge.function(Element.checkVisibility, .{});
|
||||
pub const clientWidth = bridge.accessor(Element.getClientWidth, null, .{});
|
||||
pub const clientHeight = bridge.accessor(Element.getClientHeight, null, .{});
|
||||
pub const clientTop = bridge.accessor(Element.getClientTop, null, .{});
|
||||
pub const clientLeft = bridge.accessor(Element.getClientLeft, null, .{});
|
||||
pub const scrollTop = bridge.accessor(Element.getScrollTop, Element.setScrollTop, .{});
|
||||
pub const scrollLeft = bridge.accessor(Element.getScrollLeft, Element.setScrollLeft, .{});
|
||||
pub const scrollHeight = bridge.accessor(Element.getScrollHeight, null, .{});
|
||||
pub const scrollWidth = bridge.accessor(Element.getScrollWidth, null, .{});
|
||||
pub const offsetTop = bridge.accessor(Element.getOffsetTop, null, .{});
|
||||
pub const offsetLeft = bridge.accessor(Element.getOffsetLeft, null, .{});
|
||||
pub const offsetWidth = bridge.accessor(Element.getOffsetWidth, null, .{});
|
||||
pub const offsetHeight = bridge.accessor(Element.getOffsetHeight, null, .{});
|
||||
pub const getClientRects = bridge.function(Element.getClientRects, .{});
|
||||
pub const getBoundingClientRect = bridge.function(Element.getBoundingClientRect, .{});
|
||||
pub const getElementsByTagName = bridge.function(Element.getElementsByTagName, .{});
|
||||
|
||||
Reference in New Issue
Block a user