From 2babfad486237155ef8eb363336c953cfabb04ee Mon Sep 17 00:00:00 2001 From: Karl Seguin Date: Fri, 12 Jun 2026 09:13:47 +0800 Subject: [PATCH] minor: Create ViewPort type Move logic that was added to MediaQuery into its own ViewPort. --- src/browser/StyleManager.zig | 3 ++- src/browser/Viewport.zig | 27 +++++++++++++++++++++ src/browser/css/MediaQuery.zig | 14 +---------- src/browser/webapi/IntersectionObserver.zig | 9 +++---- src/browser/webapi/Screen.zig | 8 +++--- src/browser/webapi/VisualViewport.zig | 6 ++--- src/browser/webapi/Window.zig | 16 +++++------- src/browser/webapi/css/MediaQueryList.zig | 7 +++--- src/cdp/domains/page.zig | 6 ++--- 9 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 src/browser/Viewport.zig diff --git a/src/browser/StyleManager.zig b/src/browser/StyleManager.zig index 1fd6c4063..e20e7af0d 100644 --- a/src/browser/StyleManager.zig +++ b/src/browser/StyleManager.zig @@ -20,6 +20,7 @@ const std = @import("std"); const lp = @import("lightpanda"); const Frame = @import("Frame.zig"); +const Viewport = @import("Viewport.zig"); const CssParser = @import("css/Parser.zig"); const MediaQuery = @import("css/MediaQuery.zig"); @@ -147,7 +148,7 @@ fn applyMediaAtRule(self: *StyleManager, text: []const u8, depth: u8) !void { const query = std.mem.trim(u8, rest[0..open], &std.ascii.whitespace); const inner = rest[open + 1 .. close]; - if (!MediaQuery.matches(query, MediaQuery.Viewport.default)) return; + if (!MediaQuery.matches(query, Viewport.default)) return; var it = CssParser.parseStylesheet(inner); while (it.next()) |nested_rule| { diff --git a/src/browser/Viewport.zig b/src/browser/Viewport.zig new file mode 100644 index 000000000..d6c1b8e39 --- /dev/null +++ b/src/browser/Viewport.zig @@ -0,0 +1,27 @@ +// Copyright (C) 2023-2026 Lightpanda (Selecy SAS) +// +// Francis Bouvier +// Pierre Tachoire +// +// 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 . + +const Viewport = @This(); + +width: u32, +height: u32, + +pub const default = Viewport{ + .width = 1920, + .height = 1080, +}; diff --git a/src/browser/css/MediaQuery.zig b/src/browser/css/MediaQuery.zig index f40269661..ce30dffbe 100644 --- a/src/browser/css/MediaQuery.zig +++ b/src/browser/css/MediaQuery.zig @@ -39,19 +39,7 @@ const std = @import("std"); -pub const Viewport = struct { - width: u32, - height: u32, - - /// Mirrors the hardcoded values exposed by `Window.innerWidth` / - /// `innerHeight`, `Screen.width` / `height`, and `VisualViewport.width` / - /// `height`. When viewport emulation lands, this is the single helper to - /// rewire so the cascade and `matchMedia` move together. - pub const default = Viewport{ - .width = 1920, - .height = 1080, - }; -}; +const Viewport = @import("../Viewport.zig"); /// Returns true if `query` matches the given viewport. Comma-separated /// queries are evaluated independently and combined with OR. diff --git a/src/browser/webapi/IntersectionObserver.zig b/src/browser/webapi/IntersectionObserver.zig index 3ad97696f..da7f288b9 100644 --- a/src/browser/webapi/IntersectionObserver.zig +++ b/src/browser/webapi/IntersectionObserver.zig @@ -22,7 +22,7 @@ const js = @import("../js/js.zig"); const Page = @import("../Page.zig"); const Frame = @import("../Frame.zig"); -const MediaQuery = @import("../css/MediaQuery.zig"); +const Viewport = @import("../Viewport.zig"); const Node = @import("Node.zig"); const Element = @import("Element.zig"); @@ -205,16 +205,15 @@ fn calculateIntersection( ) !IntersectionData { const target_rect = target.getBoundingClientRect(frame); - // Use root element's rect or viewport (simplified: assume 1920x1080) + // Use root element's rect or the faux-layout viewport. const root_rect = if (self._root) |root| root.getBoundingClientRect(frame) else - // Simplified viewport sourced from the single MediaQuery.Viewport.default. DOMRect{ ._x = 0.0, ._y = 0.0, - ._width = @floatFromInt(MediaQuery.Viewport.default.width), - ._height = @floatFromInt(MediaQuery.Viewport.default.height), + ._width = @floatFromInt(Viewport.default.width), + ._height = @floatFromInt(Viewport.default.height), }; // For a headless browser without real layout, we treat all elements as fully visible. diff --git a/src/browser/webapi/Screen.zig b/src/browser/webapi/Screen.zig index e912a8285..72b69f5e1 100644 --- a/src/browser/webapi/Screen.zig +++ b/src/browser/webapi/Screen.zig @@ -18,7 +18,7 @@ const js = @import("../js/js.zig"); const Frame = @import("../Frame.zig"); -const MediaQuery = @import("../css/MediaQuery.zig"); +const Viewport = @import("../Viewport.zig"); const EventTarget = @import("EventTarget.zig"); pub fn registerTypes() []const type { @@ -55,9 +55,9 @@ pub const JsApi = struct { pub var class_id: bridge.ClassId = undefined; }; - pub const width = bridge.property(MediaQuery.Viewport.default.width, .{ .template = false }); - pub const height = bridge.property(MediaQuery.Viewport.default.height, .{ .template = false }); - pub const availWidth = bridge.property(MediaQuery.Viewport.default.width, .{ .template = false }); + pub const width = bridge.property(Viewport.default.width, .{ .template = false }); + pub const height = bridge.property(Viewport.default.height, .{ .template = false }); + pub const availWidth = bridge.property(Viewport.default.width, .{ .template = false }); pub const availHeight = bridge.property(1040, .{ .template = false }); pub const colorDepth = bridge.property(24, .{ .template = false }); pub const pixelDepth = bridge.property(24, .{ .template = false }); diff --git a/src/browser/webapi/VisualViewport.zig b/src/browser/webapi/VisualViewport.zig index f2f56c4df..23c59bfbc 100644 --- a/src/browser/webapi/VisualViewport.zig +++ b/src/browser/webapi/VisualViewport.zig @@ -18,7 +18,7 @@ const js = @import("../js/js.zig"); const Frame = @import("../Frame.zig"); -const MediaQuery = @import("../css/MediaQuery.zig"); +const Viewport = @import("../Viewport.zig"); const EventTarget = @import("EventTarget.zig"); const VisualViewport = @This(); @@ -52,7 +52,7 @@ pub const JsApi = struct { 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(MediaQuery.Viewport.default.width, .{ .template = false }); - pub const height = bridge.property(MediaQuery.Viewport.default.height, .{ .template = false }); + pub const width = bridge.property(Viewport.default.width, .{ .template = false }); + pub const height = bridge.property(Viewport.default.height, .{ .template = false }); pub const scale = bridge.property(1.0, .{ .template = false }); }; diff --git a/src/browser/webapi/Window.zig b/src/browser/webapi/Window.zig index 4726c8b6b..6adebfa7e 100644 --- a/src/browser/webapi/Window.zig +++ b/src/browser/webapi/Window.zig @@ -23,7 +23,7 @@ const builtin = @import("builtin"); const js = @import("../js/js.zig"); const URL = @import("../URL.zig"); const Frame = @import("../Frame.zig"); -const MediaQuery = @import("../css/MediaQuery.zig"); +const Viewport = @import("../Viewport.zig"); const Console = @import("Console.zig"); const History = @import("History.zig"); const Navigation = @import("navigation/Navigation.zig"); @@ -55,12 +55,6 @@ 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). -// Sourced from the single MediaQuery.Viewport.default so the cascade, -// matchMedia and the window/screen dimensions stay in sync. -const DEFAULT_INNER_HEIGHT: u32 = MediaQuery.Viewport.default.height; - const Allocator = std.mem.Allocator; const Execution = js.Execution; @@ -708,8 +702,10 @@ pub fn getScrollY(self: *const Window) u32 { return self._scroll_pos.y; } +// Faux-layout viewport height, used to decide whether an element is already +// within view (e.g. scrollIntoViewIfNeeded). pub fn getInnerHeight(_: *const Window) u32 { - return DEFAULT_INNER_HEIGHT; + return Viewport.default.height; } const ScrollToOpts = union(enum) { @@ -1013,8 +1009,8 @@ pub const JsApi = struct { pub const isSecureContext = bridge.property(false, .{ .template = false }); // [Replaceable] (CSSOM-View): writable so assignment overwrites rather than throws. - pub const innerWidth = bridge.property(MediaQuery.Viewport.default.width, .{ .template = false, .readonly = false }); - pub const innerHeight = bridge.property(MediaQuery.Viewport.default.height, .{ .template = false, .readonly = false }); + pub const innerWidth = bridge.property(Viewport.default.width, .{ .template = false, .readonly = false }); + pub const innerHeight = bridge.property(Viewport.default.height, .{ .template = false, .readonly = false }); pub const devicePixelRatio = bridge.property(1, .{ .template = false, .readonly = false }); pub const opener = bridge.accessor(Window.getOpener, null, .{}); diff --git a/src/browser/webapi/css/MediaQueryList.zig b/src/browser/webapi/css/MediaQueryList.zig index e2938ace2..9a2f34bef 100644 --- a/src/browser/webapi/css/MediaQueryList.zig +++ b/src/browser/webapi/css/MediaQueryList.zig @@ -20,6 +20,7 @@ const js = @import("../../js/js.zig"); const EventTarget = @import("../EventTarget.zig"); const MediaQuery = @import("../../css/MediaQuery.zig"); +const Viewport = @import("../../Viewport.zig"); const MediaQueryList = @This(); @@ -40,10 +41,10 @@ pub fn getMedia(self: *const MediaQueryList) []const u8 { /// Re-evaluates the stored query against the current viewport on every call /// so the result stays in sync if viewport emulation later lands. The -/// viewport currently comes from `MediaQuery.Viewport.default` (1920×1080), -/// matching `Window.innerWidth` / `innerHeight`. +/// viewport currently comes from `Viewport.default` (1920×1080), matching +/// `Window.innerWidth` / `innerHeight`. pub fn getMatches(self: *const MediaQueryList) bool { - return MediaQuery.matches(self._media, MediaQuery.Viewport.default); + return MediaQuery.matches(self._media, Viewport.default); } // TODO: once viewport emulation lands, `change` events need to fire on diff --git a/src/cdp/domains/page.zig b/src/cdp/domains/page.zig index 9201050ec..c1018ec40 100644 --- a/src/cdp/domains/page.zig +++ b/src/cdp/domains/page.zig @@ -29,7 +29,6 @@ const CDP = @import("../CDP.zig"); const js = @import("../../browser/js/js.zig"); const URL = @import("../../browser/URL.zig"); const Frame = @import("../../browser/Frame.zig"); -const MediaQuery = @import("../../browser/css/MediaQuery.zig"); const timestampF = @import("../../datetime.zig").timestamp; const Notification = @import("../../Notification.zig"); @@ -907,8 +906,9 @@ fn printToPDF(cmd: *CDP.Command) !void { } fn getLayoutMetrics(cmd: *CDP.Command) !void { - const width = MediaQuery.Viewport.default.width; - const height = MediaQuery.Viewport.default.height; + const viewport = @import("../../browser/Viewport.zig").default; + const width = viewport.width; + const height = viewport.height; return cmd.sendResult(.{ .layoutViewport = .{