mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-06-11 01:25:53 -04:00
implement the IfNeeded part of scrollIntoViewIfNeeded
This commit is contained in:
@@ -125,14 +125,21 @@
|
||||
const targetY = deep.getBoundingClientRect().y;
|
||||
testing.expectTrue(targetY > 0);
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
testing.expectEqual(0, window.scrollY);
|
||||
|
||||
// scrollIntoView() always brings the element's top to the scroll offset.
|
||||
window.scrollTo(0, 999999);
|
||||
deep.scrollIntoView();
|
||||
testing.expectEqual(targetY, window.scrollY);
|
||||
|
||||
// scrollIntoViewIfNeeded behaves the same here (brings the element in view).
|
||||
window.scrollTo(0, 0);
|
||||
// scrollIntoViewIfNeeded() is a no-op when the element is already within the
|
||||
// viewport band [scrollY, scrollY + innerHeight]. Sit just below the
|
||||
// element's top so a stray scroll would visibly move scrollY.
|
||||
const inView = Math.max(0, targetY - 1);
|
||||
window.scrollTo(0, inView);
|
||||
deep.scrollIntoViewIfNeeded();
|
||||
testing.expectEqual(inView, window.scrollY);
|
||||
|
||||
// ...but it does scroll when the element is outside the band.
|
||||
window.scrollTo(0, targetY + window.innerHeight + 100);
|
||||
deep.scrollIntoViewIfNeeded();
|
||||
testing.expectEqual(targetY, window.scrollY);
|
||||
}
|
||||
|
||||
28
src/browser/tests/window/scroll_dedup.html
Normal file
28
src/browser/tests/window/scroll_dedup.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<script src="../testing.js"></script>
|
||||
<!-- Chrome don't scroll if the body isn't big enough. -->
|
||||
<body style=height:4000px;width:4000px></body>
|
||||
|
||||
<script id=scroll_dedup type=module>
|
||||
{
|
||||
const state = await testing.async();
|
||||
|
||||
let scrollevt = 0;
|
||||
document.addEventListener("scroll", () => {
|
||||
scrollevt++;
|
||||
});
|
||||
|
||||
// First move to a fresh position dispatches a scroll event (async).
|
||||
window.scrollTo(100, 200);
|
||||
// Scrolling to the same position must NOT dispatch a second scroll event.
|
||||
window.scrollTo(100, 200);
|
||||
|
||||
window.setTimeout(() => {
|
||||
state.resolve();
|
||||
}, 100);
|
||||
|
||||
await state.done(() => {
|
||||
testing.expectEqual(1, scrollevt);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -1475,6 +1475,12 @@ pub fn clone(self: *Element, deep: bool, frame: *Frame) !*Node {
|
||||
|
||||
pub fn scrollIntoViewIfNeeded(self: *Element, center_if_needed: ?bool, frame: *Frame) void {
|
||||
_ = center_if_needed;
|
||||
const y = calculateDocumentPosition(self.asNode());
|
||||
const scroll_y: f64 = @floatFromInt(frame.window.getScrollY());
|
||||
const viewport_height: f64 = @floatFromInt(frame.window.getInnerHeight());
|
||||
if (y >= scroll_y and y <= scroll_y + viewport_height) {
|
||||
return;
|
||||
}
|
||||
self.scrollIntoView(null, frame);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ const Notification = @import("../../Notification.zig");
|
||||
const log = lp.log;
|
||||
const IS_DEBUG = builtin.mode == .Debug;
|
||||
|
||||
// Faux-layout viewport height. Exposed as window.innerHeight and used to decide
|
||||
// whether an element is already within view (e.g. scrollIntoViewIfNeeded).
|
||||
const DEFAULT_INNER_HEIGHT: u32 = 1080;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Execution = js.Execution;
|
||||
|
||||
@@ -700,6 +704,10 @@ pub fn getScrollY(self: *const Window) u32 {
|
||||
return self._scroll_pos.y;
|
||||
}
|
||||
|
||||
pub fn getInnerHeight(_: *const Window) u32 {
|
||||
return DEFAULT_INNER_HEIGHT;
|
||||
}
|
||||
|
||||
const ScrollToOpts = union(enum) {
|
||||
x: i32,
|
||||
opts: Opts,
|
||||
@@ -711,17 +719,17 @@ const ScrollToOpts = union(enum) {
|
||||
};
|
||||
};
|
||||
pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, frame: *Frame) !void {
|
||||
switch (opts) {
|
||||
.x => |x| {
|
||||
self._scroll_pos.x = @intCast(@max(x, 0));
|
||||
self._scroll_pos.y = @intCast(@max(0, y orelse 0));
|
||||
},
|
||||
.opts => |o| {
|
||||
self._scroll_pos.x = @intCast(@max(0, o.left));
|
||||
self._scroll_pos.y = @intCast(@max(0, o.top));
|
||||
},
|
||||
const new_x: u32, const new_y: u32 = switch (opts) {
|
||||
.x => |x| .{@intCast(@max(x, 0)), @intCast(@max(0, y orelse 0))},
|
||||
.opts => |o| .{@intCast(@max(0, o.left)), @intCast(@max(0, o.top))},
|
||||
};
|
||||
|
||||
if (new_x == self._scroll_pos.x and new_y == self._scroll_pos.y) {
|
||||
return;
|
||||
}
|
||||
|
||||
self._scroll_pos.x = new_x;
|
||||
self._scroll_pos.y = new_y;
|
||||
self._scroll_pos.state = .scroll;
|
||||
|
||||
// We dispatch scroll event asynchronously after 10ms. So we can throttle
|
||||
@@ -1002,7 +1010,7 @@ pub const JsApi = struct {
|
||||
|
||||
// [Replaceable] (CSSOM-View): writable so assignment overwrites rather than throws.
|
||||
pub const innerWidth = bridge.property(1920, .{ .template = false, .readonly = false });
|
||||
pub const innerHeight = bridge.property(1080, .{ .template = false, .readonly = false });
|
||||
pub const innerHeight = bridge.property(DEFAULT_INNER_HEIGHT, .{ .template = false, .readonly = false });
|
||||
pub const devicePixelRatio = bridge.property(1, .{ .template = false, .readonly = false });
|
||||
|
||||
pub const opener = bridge.accessor(Window.getOpener, null, .{});
|
||||
|
||||
Reference in New Issue
Block a user