From 36bf34d46241086241f9f11b543cafd1617b4cd1 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 5 Sep 2026 12:02:06 -0700 Subject: [PATCH 1/2] Fix issue #14: OSR mouse wheel scroll direction is inverted N_SendMouseWheelEvent passed the raw AWT MouseWheelEvent.getWheelRotation()/ getUnitsToScroll() value straight through as CefMouseEvent's deltaX/deltaY with no sign adjustment. AWT's convention (positive rotation/units = scroll content down/right) is the opposite of CefMouseEvent's deltaX/ deltaY convention for the same gesture. Negate delta before assigning it to deltaX/deltaY, covering both the vertical (WHEEL_UNIT_SCROLL/ WHEEL_BLOCK_SCROLL) and Shift-held horizontal paths. Adds OsrMouseWheelDirectionTest, a minimal standalone regression test (master has no shared-browser test harness, so this uses TestFrame directly rather than depending on that infrastructure): dispatches a synthetic AWT wheel event with a positive rotation against an OSR browser and asserts window.scrollY increases. Verified against a full local suite run (124/124 passing). Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01HBFmQs6JDypYP5qdC99yDq --- .../OsrMouseWheelDirectionTest.java | 102 ++++++++++++++++++ native/CefBrowser_N.cpp | 10 +- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 java/tests/junittests/OsrMouseWheelDirectionTest.java diff --git a/java/tests/junittests/OsrMouseWheelDirectionTest.java b/java/tests/junittests/OsrMouseWheelDirectionTest.java new file mode 100644 index 00000000..6bc81f45 --- /dev/null +++ b/java/tests/junittests/OsrMouseWheelDirectionTest.java @@ -0,0 +1,102 @@ +// 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]) { + dispatchedWheel[0] = true; + initialScrollY[0] = + Double.parseDouble(title.substring("ready:".length())); + + 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]) { + gotScrollEvent[0] = true; + finalScrollY[0] = + Double.parseDouble(title.substring("scrolled:".length())); + 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); } From 47c4bb9bc4d760ea43a9e993e9d41d6812a437c6 Mon Sep 17 00:00:00 2001 From: Karl Nelson Date: Sat, 5 Sep 2026 12:23:03 -0700 Subject: [PATCH 2/2] Guard OsrMouseWheelDirectionTest's title parsing against malformed input CodeQL flagged both Double.parseDouble() calls in the onTitleChange callback as an uncaught-NumberFormatException risk (#263, #264). An exception there would escape on CEF's native callback thread instead of failing the test cleanly. Catch it and leave the corresponding flag unset so the existing post-awaitCompletion assertions report a normal test failure instead. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01HBFmQs6JDypYP5qdC99yDq --- .../OsrMouseWheelDirectionTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/java/tests/junittests/OsrMouseWheelDirectionTest.java b/java/tests/junittests/OsrMouseWheelDirectionTest.java index 6bc81f45..8f0ef096 100644 --- a/java/tests/junittests/OsrMouseWheelDirectionTest.java +++ b/java/tests/junittests/OsrMouseWheelDirectionTest.java @@ -59,9 +59,18 @@ protected void setupTest() { @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; - initialScrollY[0] = - Double.parseDouble(title.substring("ready:".length())); Component canvas = browser.getUIComponent(); canvas.requestFocusInWindow(); @@ -76,9 +85,13 @@ public void onTitleChange(CefBrowser browser, String title) { 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; - finalScrollY[0] = - Double.parseDouble(title.substring("scrolled:".length())); done.countDown(); terminateTest(); }