diff --git a/java/tests/junittests/OsrMouseWheelDirectionTest.java b/java/tests/junittests/OsrMouseWheelDirectionTest.java new file mode 100644 index 00000000..8f0ef096 --- /dev/null +++ b/java/tests/junittests/OsrMouseWheelDirectionTest.java @@ -0,0 +1,115 @@ +// Copyright (c) 2026 The Chromium Embedded Framework Authors. All rights +// reserved. Use of this source code is governed by a BSD-style license that +// can be found in the LICENSE file. + +package tests.junittests; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.cef.browser.CefBrowser; +import org.cef.handler.CefDisplayHandlerAdapter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import java.awt.Component; +import java.awt.event.MouseWheelEvent; +import java.util.concurrent.CountDownLatch; + +// Regression test for upstream chromiumembedded/java-cef#26 / this repo's +// #14: mouse wheel direction was inverted in OSR mode. +// native/CefBrowser_N.cpp's N_SendMouseWheelEvent used to pass the raw AWT +// MouseWheelEvent.getWheelRotation() value straight through as CEF's deltaY +// with no sign adjustment. AWT's convention (positive wheelRotation = wheel +// rotated away from the user, the typical "scroll down" gesture) is the +// opposite sign of what Chromium's CefMouseEvent deltaY convention expects +// for the same gesture. Fixed by negating delta before assigning it to +// deltaX/deltaY. +@ExtendWith(TestSetupExtension.class) +class OsrMouseWheelDirectionTest { + // window.scrollY on an OSR browser is a non-integer float (device-pixel- + // ratio rounding), so titles carry it as a double, not an int. + private static final String CONTENT = "" + + "
tall content
" + + ""; + + @Test + void positiveWheelRotationScrollsDown() { + final String testUrl = "http://test.com/osr_wheel_test.html"; + double[] initialScrollY = {-1}; + double[] finalScrollY = {-1}; + boolean[] dispatchedWheel = {false}; + boolean[] gotScrollEvent = {false}; + CountDownLatch done = new CountDownLatch(1); + + TestFrame frame = new TestFrame() { + @Override + protected void setupTest() { + addResource(testUrl, CONTENT, "text/html"); + client_.addDisplayHandler(new CefDisplayHandlerAdapter() { + @Override + public void onTitleChange(CefBrowser browser, String title) { + if (title.startsWith("ready:") && !dispatchedWheel[0]) { + try { + initialScrollY[0] = + Double.parseDouble(title.substring("ready:".length())); + } catch (NumberFormatException e) { + // Leave dispatchedWheel[0] false; the + // post-awaitCompletion assertion below + // reports this as a clean test failure + // instead of an uncaught exception on the + // native callback thread. + return; + } + dispatchedWheel[0] = true; + + Component canvas = browser.getUIComponent(); + canvas.requestFocusInWindow(); + long now = System.currentTimeMillis(); + // getWheelRotation() > 0: AWT's "wheel rotated + // away from the user" convention, i.e. a normal + // "scroll down" gesture in every native + // application/browser. + for (int i = 0; i < 5; i++) { + canvas.dispatchEvent(new MouseWheelEvent(canvas, + MouseWheelEvent.MOUSE_WHEEL, now + i, 0, 50, 50, 0, false, + MouseWheelEvent.WHEEL_UNIT_SCROLL, 10, 10)); + } + } else if (title.startsWith("scrolled:") && !gotScrollEvent[0]) { + try { + finalScrollY[0] = + Double.parseDouble(title.substring("scrolled:".length())); + } catch (NumberFormatException e) { + return; + } + gotScrollEvent[0] = true; + done.countDown(); + terminateTest(); + } + } + }); + createBrowser(testUrl, true /* useOSR */); + super.setupTest(); + } + }; + + frame.awaitCompletion(); + + assertTrue(dispatchedWheel[0], + "Never reached the ready state to dispatch the wheel event"); + assertTrue(gotScrollEvent[0], + "Page never reported a scroll event after the synthetic wheel event"); + assertTrue(finalScrollY[0] > initialScrollY[0], + "A positive (AWT 'scroll down') wheel rotation should increase window.scrollY " + + "-- got initial=" + initialScrollY[0] + " final=" + finalScrollY[0]); + } +} diff --git a/native/CefBrowser_N.cpp b/native/CefBrowser_N.cpp index 07f22de6..a1b4157b 100644 --- a/native/CefBrowser_N.cpp +++ b/native/CefBrowser_N.cpp @@ -2021,11 +2021,17 @@ Java_org_cef_browser_CefBrowser_1N_N_1SendMouseWheelEvent( CallJNIMethodI_V(env, cls, mouse_wheel_event, "getUnitsToScroll", &delta); } + // AWT's MouseWheelEvent convention is positive rotation/units == scroll + // content down/right, but CefMouseEvent's deltaX/deltaY (matching the + // native "wheel delta") is the opposite: positive == scroll content + // up/left. Negate so a given physical wheel gesture scrolls the same + // direction it would in a native browser window. See + // Thrameos/java-cef#14 (matches upstream cefclient issue #26). double deltaX = 0, deltaY = 0; if (cef_event.modifiers & EVENTFLAG_SHIFT_DOWN) - deltaX = delta; + deltaX = -delta; else - deltaY = delta; + deltaY = -delta; browser->GetHost()->SendMouseWheelEvent(cef_event, deltaX, deltaY); }