mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-02-04 14:33:47 +00:00
Merge pull request #1287 from lightpanda-io/window.scrollTo
Add Window.scrollTo
This commit is contained in:
42
src/browser/tests/window/scroll.html
Normal file
42
src/browser/tests/window/scroll.html
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!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_evt>
|
||||||
|
testing.async(async (restore) => {
|
||||||
|
let scrollevt = 0;
|
||||||
|
let scrollendevt = 0;
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
document.addEventListener("scroll", (event) => {
|
||||||
|
scrollevt++;
|
||||||
|
});
|
||||||
|
document.addEventListener("scrollend", (event) => {
|
||||||
|
scrollendevt++;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.scrollTo(10, 20);
|
||||||
|
testing.expectEqual(0, scrollevt);
|
||||||
|
testing.expectEqual(0, scrollendevt);
|
||||||
|
|
||||||
|
// scroll immediately: the scroll event must be throttled.
|
||||||
|
window.scrollTo(20, 40);
|
||||||
|
testing.expectEqual(0, scrollevt);
|
||||||
|
testing.expectEqual(0, scrollendevt);
|
||||||
|
|
||||||
|
// wait 10ms and scroll again: we should have a 2nd scroll, but no scrollend.
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.scrollTo(30, 40);
|
||||||
|
}, 10);
|
||||||
|
|
||||||
|
// wait until scrollend happens.
|
||||||
|
window.setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
restore();
|
||||||
|
testing.expectEqual(2, scrollevt);
|
||||||
|
testing.expectEqual(1, scrollendevt);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -33,6 +33,7 @@ const Performance = @import("Performance.zig");
|
|||||||
const Document = @import("Document.zig");
|
const Document = @import("Document.zig");
|
||||||
const Location = @import("Location.zig");
|
const Location = @import("Location.zig");
|
||||||
const Fetch = @import("net/Fetch.zig");
|
const Fetch = @import("net/Fetch.zig");
|
||||||
|
const Event = @import("Event.zig");
|
||||||
const EventTarget = @import("EventTarget.zig");
|
const EventTarget = @import("EventTarget.zig");
|
||||||
const ErrorEvent = @import("event/ErrorEvent.zig");
|
const ErrorEvent = @import("event/ErrorEvent.zig");
|
||||||
const MessageEvent = @import("event/MessageEvent.zig");
|
const MessageEvent = @import("event/MessageEvent.zig");
|
||||||
@@ -62,6 +63,19 @@ _location: *Location,
|
|||||||
_timer_id: u30 = 0,
|
_timer_id: u30 = 0,
|
||||||
_timers: std.AutoHashMapUnmanaged(u32, *ScheduleCallback) = .{},
|
_timers: std.AutoHashMapUnmanaged(u32, *ScheduleCallback) = .{},
|
||||||
_custom_elements: CustomElementRegistry = .{},
|
_custom_elements: CustomElementRegistry = .{},
|
||||||
|
_scroll_pos: struct {
|
||||||
|
x: u32,
|
||||||
|
y: u32,
|
||||||
|
state: enum {
|
||||||
|
scroll,
|
||||||
|
end,
|
||||||
|
done,
|
||||||
|
},
|
||||||
|
} = .{
|
||||||
|
.x = 0,
|
||||||
|
.y = 0,
|
||||||
|
.state = .done,
|
||||||
|
},
|
||||||
|
|
||||||
pub fn asEventTarget(self: *Window) *EventTarget {
|
pub fn asEventTarget(self: *Window) *EventTarget {
|
||||||
return self._proto;
|
return self._proto;
|
||||||
@@ -355,12 +369,90 @@ pub fn getInnerHeight(_: *const Window) u32 {
|
|||||||
return 1080;
|
return 1080;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getScrollX(_: *const Window) u32 {
|
pub fn getScrollX(self: *const Window) u32 {
|
||||||
return 0;
|
return self._scroll_pos.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getScrollY(_: *const Window) u32 {
|
pub fn getScrollY(self: *const Window) u32 {
|
||||||
return 0;
|
return self._scroll_pos.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ScrollToOpts = union(enum) {
|
||||||
|
x: i32,
|
||||||
|
opts: Opts,
|
||||||
|
|
||||||
|
const Opts = struct {
|
||||||
|
top: i32,
|
||||||
|
left: i32,
|
||||||
|
behavior: []const u8 = "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
pub fn scrollTo(self: *Window, opts: ScrollToOpts, y: ?i32, page: *Page) !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));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
self._scroll_pos.state = .scroll;
|
||||||
|
|
||||||
|
// We dispatch scroll event asynchronously after 10ms. So we can throttle
|
||||||
|
// them.
|
||||||
|
try page.scheduler.add(
|
||||||
|
page,
|
||||||
|
struct {
|
||||||
|
fn dispatch(_page: *anyopaque) anyerror!?u32 {
|
||||||
|
const p: *Page = @ptrCast(@alignCast(_page));
|
||||||
|
const pos = &p.window._scroll_pos;
|
||||||
|
// If the state isn't scroll, we can ignore safely to throttle
|
||||||
|
// the events.
|
||||||
|
if (pos.state != .scroll) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = try Event.init("scroll", .{ .bubbles = true }, p);
|
||||||
|
try p._event_manager.dispatch(p.document.asEventTarget(), event);
|
||||||
|
|
||||||
|
pos.state = .end;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.dispatch,
|
||||||
|
10,
|
||||||
|
.{ .low_priority = true },
|
||||||
|
);
|
||||||
|
// We dispatch scrollend event asynchronously after 20ms.
|
||||||
|
try page.scheduler.add(
|
||||||
|
page,
|
||||||
|
struct {
|
||||||
|
fn dispatch(_page: *anyopaque) anyerror!?u32 {
|
||||||
|
const p: *Page = @ptrCast(@alignCast(_page));
|
||||||
|
const pos = &p.window._scroll_pos;
|
||||||
|
// Dispatch only if the state is .end.
|
||||||
|
// If a scroll is pending, retry in 10ms.
|
||||||
|
// If the state is .end, the event has been dispatched, so
|
||||||
|
// ignore safely.
|
||||||
|
switch (pos.state) {
|
||||||
|
.scroll => return 10,
|
||||||
|
.end => {},
|
||||||
|
.done => return null,
|
||||||
|
}
|
||||||
|
const event = try Event.init("scrollend", .{ .bubbles = true }, p);
|
||||||
|
try p._event_manager.dispatch(p.document.asEventTarget(), event);
|
||||||
|
|
||||||
|
pos.state = .done;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.dispatch,
|
||||||
|
20,
|
||||||
|
.{ .low_priority = true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScheduleOpts = struct {
|
const ScheduleOpts = struct {
|
||||||
@@ -571,6 +663,8 @@ pub const JsApi = struct {
|
|||||||
pub const scrollY = bridge.accessor(Window.getScrollY, null, .{ .cache = "scrollY" });
|
pub const scrollY = bridge.accessor(Window.getScrollY, null, .{ .cache = "scrollY" });
|
||||||
pub const pageXOffset = bridge.accessor(Window.getScrollX, null, .{ .cache = "pageXOffset" });
|
pub const pageXOffset = bridge.accessor(Window.getScrollX, null, .{ .cache = "pageXOffset" });
|
||||||
pub const pageYOffset = bridge.accessor(Window.getScrollY, null, .{ .cache = "pageYOffset" });
|
pub const pageYOffset = bridge.accessor(Window.getScrollY, null, .{ .cache = "pageYOffset" });
|
||||||
|
pub const scrollTo = bridge.function(Window.scrollTo, .{});
|
||||||
|
pub const scroll = bridge.function(Window.scrollTo, .{});
|
||||||
};
|
};
|
||||||
|
|
||||||
const testing = @import("../../testing.zig");
|
const testing = @import("../../testing.zig");
|
||||||
|
|||||||
Reference in New Issue
Block a user