minor: Create ViewPort type

Move logic that was added to MediaQuery into its own ViewPort.
This commit is contained in:
Karl Seguin
2026-06-12 09:13:47 +08:00
parent 331aa6a71a
commit 2babfad486
9 changed files with 54 additions and 42 deletions

View File

@@ -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| {

27
src/browser/Viewport.zig Normal file
View File

@@ -0,0 +1,27 @@
// 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 Viewport = @This();
width: u32,
height: u32,
pub const default = Viewport{
.width = 1920,
.height = 1080,
};

View File

@@ -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.

View File

@@ -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.

View File

@@ -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 });

View File

@@ -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 });
};

View File

@@ -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, .{});

View File

@@ -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

View File

@@ -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 = .{