Merge pull request #1518 from lightpanda-io/visual_viewport
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
nightly build / build-linux-x86_64 (push) Has been cancelled
nightly build / build-linux-aarch64 (push) Has been cancelled
nightly build / build-macos-aarch64 (push) Has been cancelled
nightly build / build-macos-x86_64 (push) Has been cancelled
wpt / web platform tests json output (push) Has been cancelled
wpt / perf-fmt (push) Has been cancelled
e2e-integration-test / zig build release (push) Has been cancelled
e2e-integration-test / demo-integration-scripts (push) Has been cancelled

implement a dummy VisualViewport
This commit is contained in:
Karl Seguin
2026-02-11 07:14:54 +08:00
committed by GitHub
7 changed files with 93 additions and 0 deletions

View File

@@ -184,6 +184,7 @@ pub fn dispatch(self: *EventManager, target: *EventTarget, event: *Event) !void
.navigation, .navigation,
.screen, .screen,
.screen_orientation, .screen_orientation,
.visual_viewport,
.generic, .generic,
=> { => {
const list = self.lookup.get(.{ const list = self.lookup.get(.{

View File

@@ -51,6 +51,7 @@ const Document = @import("webapi/Document.zig");
const ShadowRoot = @import("webapi/ShadowRoot.zig"); const ShadowRoot = @import("webapi/ShadowRoot.zig");
const Performance = @import("webapi/Performance.zig"); const Performance = @import("webapi/Performance.zig");
const Screen = @import("webapi/Screen.zig"); const Screen = @import("webapi/Screen.zig");
const VisualViewport = @import("webapi/VisualViewport.zig");
const PerformanceObserver = @import("webapi/PerformanceObserver.zig"); const PerformanceObserver = @import("webapi/PerformanceObserver.zig");
const MutationObserver = @import("webapi/MutationObserver.zig"); const MutationObserver = @import("webapi/MutationObserver.zig");
const IntersectionObserver = @import("webapi/IntersectionObserver.zig"); const IntersectionObserver = @import("webapi/IntersectionObserver.zig");
@@ -306,6 +307,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
const storage_bucket = try self._factory.create(storage.Bucket{}); const storage_bucket = try self._factory.create(storage.Bucket{});
const screen = try Screen.init(self); const screen = try Screen.init(self);
const visual_viewport = try VisualViewport.init(self);
self.window = try self._factory.eventTarget(Window{ self.window = try self._factory.eventTarget(Window{
._document = self.document, ._document = self.document,
._storage_bucket = storage_bucket, ._storage_bucket = storage_bucket,
@@ -313,6 +315,7 @@ fn reset(self: *Page, comptime initializing: bool) !void {
._proto = undefined, ._proto = undefined,
._location = &default_location, ._location = &default_location,
._screen = screen, ._screen = screen,
._visual_viewport = visual_viewport,
}); });
self.window._document = self.document; self.window._document = self.document;
self.window._location = &default_location; self.window._location = &default_location;

View File

@@ -914,6 +914,7 @@ pub const JsApis = flattenTypes(&.{
@import("../webapi/Blob.zig"), @import("../webapi/Blob.zig"),
@import("../webapi/File.zig"), @import("../webapi/File.zig"),
@import("../webapi/Screen.zig"), @import("../webapi/Screen.zig"),
@import("../webapi/VisualViewport.zig"),
@import("../webapi/PerformanceObserver.zig"), @import("../webapi/PerformanceObserver.zig"),
@import("../webapi/navigation/Navigation.zig"), @import("../webapi/navigation/Navigation.zig"),
@import("../webapi/navigation/NavigationEventTarget.zig"), @import("../webapi/navigation/NavigationEventTarget.zig"),

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<script src="../testing.js"></script>
<script id=visual_viewport>
const vp = window.visualViewport;
testing.expectEqual(vp, window.visualViewport);
testing.expectEqual(0, vp.offsetLeft);
testing.expectEqual(0, vp.offsetTop);
testing.expectEqual(0, vp.pageLeft);
testing.expectEqual(0, vp.pageTop);
testing.expectEqual(1920, vp.width);
testing.expectEqual(1080, vp.height);
testing.expectEqual(1.0, vp.scale);
</script>

View File

@@ -42,6 +42,7 @@ pub const Type = union(enum) {
navigation: *@import("navigation/NavigationEventTarget.zig"), navigation: *@import("navigation/NavigationEventTarget.zig"),
screen: *@import("Screen.zig"), screen: *@import("Screen.zig"),
screen_orientation: *@import("Screen.zig").Orientation, screen_orientation: *@import("Screen.zig").Orientation,
visual_viewport: *@import("VisualViewport.zig"),
}; };
pub fn init(page: *Page) !*EventTarget { pub fn init(page: *Page) !*EventTarget {
@@ -132,6 +133,7 @@ pub fn format(self: *EventTarget, writer: *std.Io.Writer) !void {
.navigation => writer.writeAll("<Navigation>"), .navigation => writer.writeAll("<Navigation>"),
.screen => writer.writeAll("<Screen>"), .screen => writer.writeAll("<Screen>"),
.screen_orientation => writer.writeAll("<ScreenOrientation>"), .screen_orientation => writer.writeAll("<ScreenOrientation>"),
.visual_viewport => writer.writeAll("<VisualViewport>"),
}; };
} }
@@ -148,6 +150,7 @@ pub fn toString(self: *EventTarget) []const u8 {
.navigation => return "[object Navigation]", .navigation => return "[object Navigation]",
.screen => return "[object Screen]", .screen => return "[object Screen]",
.screen_orientation => return "[object ScreenOrientation]", .screen_orientation => return "[object ScreenOrientation]",
.visual_viewport => return "[object VisualViewport]",
}; };
} }

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
//
// Francis Bouvier <francis@lightpanda.io>
// Pierre Tachoire <pierre@lightpanda.io>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
const js = @import("../js/js.zig");
const Page = @import("../Page.zig");
const EventTarget = @import("EventTarget.zig");
const Window = @import("Window.zig");
const VisualViewport = @This();
_proto: *EventTarget,
pub fn init(page: *Page) !*VisualViewport {
return page._factory.eventTarget(VisualViewport{
._proto = undefined,
});
}
pub fn asEventTarget(self: *VisualViewport) *EventTarget {
return self._proto;
}
pub fn getPageLeft(_: *const VisualViewport, page: *Page) u32 {
return page.window.getScrollX();
}
pub fn getPageTop(_: *const VisualViewport, page: *Page) u32 {
return page.window.getScrollY();
}
pub const JsApi = struct {
pub const bridge = js.Bridge(VisualViewport);
pub const Meta = struct {
pub const name = "VisualViewport";
pub const prototype_chain = bridge.prototypeChain();
pub var class_id: bridge.ClassId = undefined;
};
// Static viewport properties for headless browser
// No pinch-zoom or mobile viewport, so values are straightforward
pub const offsetLeft = bridge.property(0, .{ .template = false });
pub const offsetTop = bridge.property(0, .{ .template = false });
pub const pageLeft = bridge.accessor(VisualViewport.getPageLeft, null, .{});
pub const pageTop = bridge.accessor(VisualViewport.getPageTop, null, .{});
pub const width = bridge.property(1920, .{ .template = false });
pub const height = bridge.property(1080, .{ .template = false });
pub const scale = bridge.property(1.0, .{ .template = false });
};

View File

@@ -29,6 +29,7 @@ const Crypto = @import("Crypto.zig");
const CSS = @import("CSS.zig"); const CSS = @import("CSS.zig");
const Navigator = @import("Navigator.zig"); const Navigator = @import("Navigator.zig");
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const VisualViewport = @import("VisualViewport.zig");
const Performance = @import("Performance.zig"); 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");
@@ -57,6 +58,7 @@ _crypto: Crypto = .init,
_console: Console = .init, _console: Console = .init,
_navigator: Navigator = .init, _navigator: Navigator = .init,
_screen: *Screen, _screen: *Screen,
_visual_viewport: *VisualViewport,
_performance: Performance, _performance: Performance,
_storage_bucket: *storage.Bucket, _storage_bucket: *storage.Bucket,
_on_load: ?js.Function.Global = null, _on_load: ?js.Function.Global = null,
@@ -110,6 +112,10 @@ pub fn getScreen(self: *Window) *Screen {
return self._screen; return self._screen;
} }
pub fn getVisualViewport(self: *const Window) *VisualViewport {
return self._visual_viewport;
}
pub fn getCrypto(self: *Window) *Crypto { pub fn getCrypto(self: *Window) *Crypto {
return &self._crypto; return &self._crypto;
} }
@@ -714,6 +720,7 @@ pub const JsApi = struct {
pub const console = bridge.accessor(Window.getConsole, null, .{}); pub const console = bridge.accessor(Window.getConsole, null, .{});
pub const navigator = bridge.accessor(Window.getNavigator, null, .{}); pub const navigator = bridge.accessor(Window.getNavigator, null, .{});
pub const screen = bridge.accessor(Window.getScreen, null, .{}); pub const screen = bridge.accessor(Window.getScreen, null, .{});
pub const visualViewport = bridge.accessor(Window.getVisualViewport, null, .{});
pub const performance = bridge.accessor(Window.getPerformance, null, .{}); pub const performance = bridge.accessor(Window.getPerformance, null, .{});
pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{}); pub const localStorage = bridge.accessor(Window.getLocalStorage, null, .{});
pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{}); pub const sessionStorage = bridge.accessor(Window.getSessionStorage, null, .{});