git.lucas.co / cce-browser
web browser (Servo)
git clone https://git.lucas.co/cce-browser.git

commit85f04f4cfdbbd9ca857c91218354107eb9e2cd99
parentbc571d121e
authorLucas Galante <[email protected]>
date2026-08-10 21:48
Fix wheel scrolling: pass winit-signed deltas, drop manual scroll event

Servo turns a Wheel input event into scrolling internally — it hit-tests,
lets the page preventDefault, then applies the *inverted* WheelDelta as
the scroll (paint's webview_renderer). So the embedder must pass deltas
in winit sign convention (positive y = scroll up), exactly as cce-ui
delivers them. The previous code negated the delta (scrolling up at the
top of every page = visible no-op) and sent a redundant
notify_scroll_event.

Live-verified via ccectl injection: wheel scroll, PageDown, URL-bar
click/typing/Enter navigation, in-page link click, back button.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/main.rs    |  9 +++++----
 src/webview.rs | 23 +++++++++--------------
 2 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 26cb670..c0a97ce 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -380,11 +380,12 @@ impl Application for BrowserApp {
         if pos.y < CHROME_H {
             return;
         }
-        // cce-ui deltas are winit-signed (positive = scroll up); the DOM and
-        // Servo's Scroll::Delta want positive = reveal content below.
+        // WheelDelta keeps cce-ui's winit sign convention (positive = scroll
+        // up); Servo inverts it into the scroll offset internally, after the
+        // page has had its preventDefault chance.
         let (dx, dy) = match delta {
-            MouseScrollDelta::LineDelta(x, y) => (-(*x as f64) * LINE_PX, -(*y as f64) * LINE_PX),
-            MouseScrollDelta::PixelDelta(p) => (-p.x, -p.y),
+            MouseScrollDelta::LineDelta(x, y) => (*x as f64 * LINE_PX, *y as f64 * LINE_PX),
+            MouseScrollDelta::PixelDelta(p) => (p.x, p.y),
         };
         let s = self.scale;
         self.host.wheel(
diff --git a/src/webview.rs b/src/webview.rs
index 985c347..c565f08 100644
--- a/src/webview.rs
+++ b/src/webview.rs
@@ -14,11 +14,10 @@ use std::rc::Rc;
 use dpi::PhysicalSize;
 use euclid::Scale;
 use servo::{
-    DeviceIntRect, DevicePoint, DeviceVector2D, EventLoopWaker, InputEvent, Key as DomKey,
-    KeyState, KeyboardEvent, LoadStatus, MouseButton as DomMouseButton, MouseButtonAction,
-    MouseButtonEvent, MouseMoveEvent, RenderingContext, Scroll, Servo, ServoBuilder,
-    SoftwareRenderingContext, WebView, WebViewBuilder, WebViewDelegate, WheelDelta, WheelEvent,
-    WheelMode,
+    DeviceIntRect, DevicePoint, EventLoopWaker, InputEvent, Key as DomKey, KeyState,
+    KeyboardEvent, LoadStatus, MouseButton as DomMouseButton, MouseButtonAction, MouseButtonEvent,
+    MouseMoveEvent, RenderingContext, Servo, ServoBuilder, SoftwareRenderingContext, WebView,
+    WebViewBuilder, WebViewDelegate, WheelDelta, WheelEvent, WheelMode,
 };
 use url::Url;
 
@@ -207,19 +206,15 @@ impl ServoHost {
         )));
     }
 
-    /// Wheel/scroll in device pixels, DOM sign convention (positive y
-    /// reveals content below). Sends both the DOM wheel event and the
-    /// compositor scroll.
+    /// Wheel in device pixels, winit sign convention (positive y = scroll
+    /// up). Servo hit-tests the wheel event, lets the page preventDefault,
+    /// and applies the inverted delta as the scroll itself — no separate
+    /// scroll event wanted.
     pub fn wheel(&self, dx_px: f64, dy_px: f64, x_px: f32, y_px: f32) {
-        let point = DevicePoint::new(x_px, y_px);
         let _ = self.webview.notify_input_event(InputEvent::Wheel(WheelEvent::new(
             WheelDelta { x: dx_px, y: dy_px, z: 0.0, mode: WheelMode::DeltaPixel },
-            point.into(),
+            DevicePoint::new(x_px, y_px).into(),
         )));
-        self.webview.notify_scroll_event(
-            Scroll::Delta(DeviceVector2D::new(dx_px as f32, dy_px as f32).into()),
-            point.into(),
-        );
     }
 
     pub fn key(&self, key: DomKey, pressed: bool) {