LibWeb+WebContent: Allow WebContent to disentangle zoom from css pixels

So Ladybird can paint scrollbar & resizer chrome at the same size
regardless of zoom level while still respecting device pixel ratio
This commit is contained in:
Jonathan Gamble 2026-01-05 19:05:10 -06:00 committed by Sam Atkins
parent 559f6bf784
commit fc22c9ea38
16 changed files with 89 additions and 39 deletions

View file

@ -330,6 +330,8 @@ public:
virtual void request_new_process_for_navigation(URL::URL const&) { }
virtual Gfx::Palette palette() const = 0;
virtual DevicePixelRect screen_rect() const = 0;
virtual double zoom_level() const = 0;
virtual double device_pixel_ratio() const = 0;
virtual double device_pixels_per_css_pixel() const = 0;
virtual CSS::PreferredColorScheme preferred_color_scheme() const = 0;
virtual CSS::PreferredContrast preferred_contrast() const = 0;

View file

@ -79,6 +79,8 @@ public:
virtual bool is_connection_open() const override { return false; }
virtual Gfx::Palette palette() const override { return m_host_page->client().palette(); }
virtual DevicePixelRect screen_rect() const override { return {}; }
virtual double zoom_level() const override { return 1.0; }
virtual double device_pixel_ratio() const override { return 1.0; }
virtual double device_pixels_per_css_pixel() const override { return 1.0; }
virtual CSS::PreferredColorScheme preferred_color_scheme() const override { return m_host_page->client().preferred_color_scheme(); }
virtual CSS::PreferredContrast preferred_contrast() const override { return m_host_page->client().preferred_contrast(); }

View file

@ -171,7 +171,7 @@ void ViewImplementation::zoom_in()
{
if (m_zoom_level >= ZOOM_MAX_LEVEL)
return;
m_zoom_level = round_to<int>((m_zoom_level + ZOOM_STEP) * 100) / 100.0f;
m_zoom_level = round_to<int>((m_zoom_level + ZOOM_STEP) * 100) / 100.0;
update_zoom();
}
@ -179,7 +179,7 @@ void ViewImplementation::zoom_out()
{
if (m_zoom_level <= ZOOM_MIN_LEVEL)
return;
m_zoom_level = round_to<int>((m_zoom_level - ZOOM_STEP) * 100) / 100.0f;
m_zoom_level = round_to<int>((m_zoom_level - ZOOM_STEP) * 100) / 100.0;
update_zoom();
}
@ -191,7 +191,7 @@ void ViewImplementation::set_zoom(double zoom_level)
void ViewImplementation::reset_zoom()
{
m_zoom_level = 1.0f;
m_zoom_level = 1.0;
update_zoom();
client().async_reset_zoom(m_client_state.page_index);
}
@ -546,14 +546,14 @@ void ViewImplementation::did_allocate_iosurface_backing_stores(i32 front_id, Cor
void ViewImplementation::update_zoom()
{
if (m_zoom_level != 1.0f) {
if (m_zoom_level != 1.0) {
m_reset_zoom_action->set_text(MUST(String::formatted("{}%", round_to<int>(m_zoom_level * 100))));
m_reset_zoom_action->set_visible(true);
} else {
m_reset_zoom_action->set_visible(false);
}
client().async_set_device_pixels_per_css_pixel(m_client_state.page_index, m_device_pixel_ratio * m_zoom_level);
client().async_set_zoom_level(m_client_state.page_index, m_zoom_level);
}
void ViewImplementation::handle_resize()
@ -583,8 +583,8 @@ void ViewImplementation::initialize_client(CreateNewClient create_new_client)
m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid());
client().async_set_window_handle(m_client_state.page_index, m_client_state.client_handle);
client().async_set_device_pixels_per_css_pixel(m_client_state.page_index, m_device_pixel_ratio);
client().async_set_zoom_level(m_client_state.page_index, m_zoom_level);
client().async_set_device_pixel_ratio(m_client_state.page_index, m_device_pixel_ratio);
client().async_set_maximum_frames_per_second(m_client_state.page_index, m_maximum_frames_per_second);
client().async_set_system_visibility_state(m_client_state.page_index, m_system_visibility_state);

View file

@ -70,8 +70,8 @@ public:
void zoom_out();
void set_zoom(double zoom_level);
void reset_zoom();
float device_pixel_ratio() const { return m_device_pixel_ratio; }
double zoom_level() const { return m_zoom_level; }
double device_pixel_ratio() const { return m_device_pixel_ratio; }
double maximum_frames_per_second() const { return m_maximum_frames_per_second; }
void enqueue_input_event(Web::InputEvent);
@ -238,9 +238,9 @@ public:
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const = 0;
protected:
static constexpr auto ZOOM_MIN_LEVEL = 0.3f;
static constexpr auto ZOOM_MAX_LEVEL = 5.0f;
static constexpr auto ZOOM_STEP = 0.1f;
static constexpr auto ZOOM_MIN_LEVEL = 0.3;
static constexpr auto ZOOM_MAX_LEVEL = 5.0;
static constexpr auto ZOOM_STEP = 0.1;
ViewImplementation();
@ -289,8 +289,8 @@ protected:
URL::URL m_url;
Utf16String m_title;
float m_zoom_level { 1.0 };
float m_device_pixel_ratio { 1.0 };
double m_zoom_level { 1.0 };
double m_device_pixel_ratio { 1.0 };
double m_maximum_frames_per_second { 60.0 };
RefPtr<Menu> m_page_context_menu;

View file

@ -1172,10 +1172,16 @@ void ConnectionFromClient::set_is_scripting_enabled(u64 page_id, bool is_scripti
page->set_is_scripting_enabled(is_scripting_enabled);
}
void ConnectionFromClient::set_device_pixels_per_css_pixel(u64 page_id, float device_pixels_per_css_pixel)
void ConnectionFromClient::set_device_pixel_ratio(u64 page_id, double device_pixel_ratio)
{
if (auto page = this->page(page_id); page.has_value())
page->set_device_pixels_per_css_pixel(device_pixels_per_css_pixel);
page->set_device_pixel_ratio(device_pixel_ratio);
}
void ConnectionFromClient::set_zoom_level(u64 page_id, double zoom_level)
{
if (auto page = this->page(page_id); page.has_value())
page->set_zoom_level(zoom_level);
}
void ConnectionFromClient::set_maximum_frames_per_second(u64 page_id, double maximum_frames_per_second)

View file

@ -114,7 +114,8 @@ private:
virtual void set_enable_global_privacy_control(u64 page_id, bool) override;
virtual void set_has_focus(u64 page_id, bool) override;
virtual void set_is_scripting_enabled(u64 page_id, bool) override;
virtual void set_device_pixels_per_css_pixel(u64 page_id, float) override;
virtual void set_zoom_level(u64 page_id, double zoom_level) override;
virtual void set_device_pixel_ratio(u64 page_id, double device_pixel_ratio) override;
virtual void set_maximum_frames_per_second(u64 page_id, double) override;
virtual void set_window_position(u64 page_id, Web::DevicePixelPoint) override;
virtual void set_window_size(u64 page_id, Web::DevicePixelSize) override;

View file

@ -54,7 +54,8 @@ public:
void set_palette_impl(Gfx::PaletteImpl&);
void set_viewport_size(Web::DevicePixelSize const&);
void set_screen_rects(Vector<Web::DevicePixelRect, 4> const& rects, size_t main_screen_index) { m_screen_rect = rects[main_screen_index]; }
void set_device_pixels_per_css_pixel(float device_pixels_per_css_pixel) { m_device_pixels_per_css_pixel = device_pixels_per_css_pixel; }
void set_device_pixel_ratio(double device_pixel_ratio) { m_device_pixel_ratio = device_pixel_ratio; }
void set_zoom_level(double zoom_level) { m_zoom_level = zoom_level; }
void set_maximum_frames_per_second(u64 maximum_frames_per_second);
void set_preferred_color_scheme(Web::CSS::PreferredColorScheme);
void set_preferred_contrast(Web::CSS::PreferredContrast);
@ -90,7 +91,9 @@ public:
Vector<Web::CSS::StyleSheetIdentifier> list_style_sheets() const;
virtual double device_pixels_per_css_pixel() const override { return m_device_pixels_per_css_pixel; }
virtual double zoom_level() const override { return m_zoom_level; }
virtual double device_pixel_ratio() const override { return m_device_pixel_ratio; }
virtual double device_pixels_per_css_pixel() const override { return m_device_pixel_ratio * m_zoom_level; }
virtual Web::DisplayListPlayerType display_list_player_type() const override;
@ -186,7 +189,8 @@ private:
GC::Ref<Web::Page> m_page;
RefPtr<Gfx::PaletteImpl> m_palette_impl;
Web::DevicePixelRect m_screen_rect;
float m_device_pixels_per_css_pixel { 1.0f };
double m_device_pixel_ratio { 1.0 };
double m_zoom_level { 1.0 };
double m_maximum_frames_per_second { 60.0 };
u64 m_id { 0 };
bool m_has_focus { false };

View file

@ -100,7 +100,8 @@ endpoint WebContentServer
set_enable_global_privacy_control(u64 page_id, bool enable) =|
set_has_focus(u64 page_id, bool has_focus) =|
set_is_scripting_enabled(u64 page_id, bool is_scripting_enabled) =|
set_device_pixels_per_css_pixel(u64 page_id, float device_pixels_per_css_pixel) =|
set_device_pixel_ratio(u64 page_id, double device_pixel_ratio) =|
set_zoom_level(u64 page_id, double zoom_level) =|
set_maximum_frames_per_second(u64 page_id, double maximum_frames_per_second) =|
set_window_position(u64 page_id, Web::DevicePixelPoint position) =|

View file

@ -57,6 +57,16 @@ Web::DevicePixelRect PageHost::screen_rect() const
return {};
}
double PageHost::zoom_level() const
{
return 1.0;
}
double PageHost::device_pixel_ratio() const
{
return 1.0;
}
double PageHost::device_pixels_per_css_pixel() const
{
return 1.0;

View file

@ -28,6 +28,8 @@ public:
virtual bool is_connection_open() const override;
virtual Gfx::Palette palette() const override;
virtual Web::DevicePixelRect screen_rect() const override;
virtual double zoom_level() const override;
virtual double device_pixel_ratio() const override;
virtual double device_pixels_per_css_pixel() const override;
virtual Web::CSS::PreferredColorScheme preferred_color_scheme() const override;
virtual Web::CSS::PreferredContrast preferred_contrast() const override;

View file

@ -61,7 +61,8 @@ void WebViewImplementationNative::initialize_client(WebView::ViewImplementation:
m_client_state.client_handle = MUST(Web::Crypto::generate_random_uuid());
client().async_set_window_handle(0, m_client_state.client_handle);
client().async_set_device_pixels_per_css_pixel(0, m_device_pixel_ratio);
client().async_set_device_pixel_ratio(0, m_device_pixel_ratio);
client().async_set_zoom_level(0, m_zoom_level);
set_system_visibility_state(Web::HTML::VisibilityState::Visible);
@ -87,10 +88,16 @@ void WebViewImplementationNative::set_viewport_geometry(int w, int h)
handle_resize();
}
void WebViewImplementationNative::set_device_pixel_ratio(float f)
void WebViewImplementationNative::set_device_pixel_ratio(double f)
{
m_device_pixel_ratio = f;
client().async_set_device_pixels_per_css_pixel(0, m_device_pixel_ratio);
client().async_set_device_pixel_ratio(0, m_device_pixel_ratio);
}
void WebViewImplementationNative::set_zoom_level(double f)
{
m_zoom_level = f;
client().async_set_zoom_level(0, m_zoom_level);
}
void WebViewImplementationNative::mouse_event(Web::MouseEvent::Type event_type, float x, float y, float raw_x, float raw_y)

View file

@ -28,7 +28,8 @@ public:
void paint_into_bitmap(void* android_bitmap_raw, AndroidBitmapInfo const& info);
void set_viewport_geometry(int w, int h);
void set_device_pixel_ratio(float f);
void set_zoom_level(double zoom_level);
void set_device_pixel_ratio(double f);
void mouse_event(Web::MouseEvent::Type event_type, float x, float y, float raw_x, float raw_y);

View file

@ -15,17 +15,17 @@
namespace Ladybird {
template<typename T>
static T scale_for_device(T size, float device_pixel_ratio)
static T scale_for_device(T size, double device_pixel_ratio)
{
return size.template to_type<float>().scaled(device_pixel_ratio).template to_type<int>();
return size.template to_type<double>().scaled(device_pixel_ratio).template to_type<int>();
}
ErrorOr<NonnullOwnPtr<WebViewBridge>> WebViewBridge::create(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, u64 maximum_frames_per_second)
ErrorOr<NonnullOwnPtr<WebViewBridge>> WebViewBridge::create(Vector<Web::DevicePixelRect> screen_rects, double device_pixel_ratio, u64 maximum_frames_per_second)
{
return adopt_nonnull_own_or_enomem(new (nothrow) WebViewBridge(move(screen_rects), device_pixel_ratio, maximum_frames_per_second));
}
WebViewBridge::WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, u64 maximum_frames_per_second)
WebViewBridge::WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, double device_pixel_ratio, u64 maximum_frames_per_second)
: m_screen_rects(move(screen_rects))
{
m_device_pixel_ratio = device_pixel_ratio;
@ -34,15 +34,21 @@ WebViewBridge::WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, float de
WebViewBridge::~WebViewBridge() = default;
void WebViewBridge::set_device_pixel_ratio(float device_pixel_ratio)
void WebViewBridge::set_device_pixel_ratio(double device_pixel_ratio)
{
m_device_pixel_ratio = device_pixel_ratio;
client().async_set_device_pixels_per_css_pixel(m_client_state.page_index, m_device_pixel_ratio * m_zoom_level);
client().async_set_device_pixel_ratio(m_client_state.page_index, m_device_pixel_ratio);
}
void WebViewBridge::set_zoom_level(double zoom_level)
{
m_zoom_level = zoom_level;
update_zoom();
}
void WebViewBridge::set_viewport_rect(Gfx::IntRect viewport_rect)
{
viewport_rect.set_size(scale_for_device(viewport_rect.size(), m_device_pixel_ratio));
viewport_rect.set_size(scale_for_device(viewport_rect.size(), device_pixel_ratio()));
m_viewport_size = viewport_rect.size();
handle_resize();
@ -117,7 +123,7 @@ Web::DevicePixelSize WebViewBridge::viewport_size() const
Gfx::IntPoint WebViewBridge::to_content_position(Gfx::IntPoint widget_position) const
{
return scale_for_device(widget_position, m_device_pixel_ratio);
return scale_for_device(widget_position, device_pixel_ratio());
}
Gfx::IntPoint WebViewBridge::to_widget_position(Gfx::IntPoint content_position) const

View file

@ -17,15 +17,15 @@ namespace Ladybird {
class WebViewBridge final : public WebView::ViewImplementation {
public:
static ErrorOr<NonnullOwnPtr<WebViewBridge>> create(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, u64 maximum_frames_per_second);
static ErrorOr<NonnullOwnPtr<WebViewBridge>> create(Vector<Web::DevicePixelRect> screen_rects, double device_pixel_ratio, u64 maximum_frames_per_second);
virtual ~WebViewBridge() override;
virtual void initialize_client(CreateNewClient = CreateNewClient::Yes) override;
void initialize_client_as_child(WebViewBridge& parent, u64 page_index);
float device_pixel_ratio() const { return m_device_pixel_ratio; }
void set_device_pixel_ratio(float device_pixel_ratio);
float inverse_device_pixel_ratio() const { return 1.0f / m_device_pixel_ratio; }
void set_device_pixel_ratio(double device_pixel_ratio);
void set_zoom_level(double zoom_level);
double inverse_device_pixel_ratio() const { return 1.0 / device_pixel_ratio(); }
void set_viewport_rect(Gfx::IntRect);
@ -49,7 +49,7 @@ public:
auto& pinch_state() { return m_pinch_state; }
private:
WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, float device_pixel_ratio, u64 maximum_frames_per_second);
WebViewBridge(Vector<Web::DevicePixelRect> screen_rects, double device_pixel_ratio, u64 maximum_frames_per_second);
virtual void update_zoom() override;
virtual Web::DevicePixelSize viewport_size() const override;

View file

@ -536,11 +536,18 @@ void WebContentView::set_viewport_rect(Gfx::IntRect rect)
void WebContentView::set_device_pixel_ratio(double device_pixel_ratio)
{
m_device_pixel_ratio = device_pixel_ratio;
client().async_set_device_pixels_per_css_pixel(m_client_state.page_index, m_device_pixel_ratio * m_zoom_level);
client().async_set_device_pixel_ratio(m_client_state.page_index, m_device_pixel_ratio);
update_viewport_size();
handle_resize();
}
void WebContentView::set_zoom_level(double zoom_level)
{
m_zoom_level = zoom_level;
client().async_set_zoom_level(m_client_state.page_index, m_zoom_level);
update_zoom();
}
void WebContentView::set_maximum_frames_per_second(double maximum_frames_per_second)
{
m_maximum_frames_per_second = maximum_frames_per_second;

View file

@ -63,6 +63,7 @@ public:
void set_viewport_rect(Gfx::IntRect);
void set_device_pixel_ratio(double);
void set_zoom_level(double);
void set_maximum_frames_per_second(double);
enum class PaletteMode {