Sitelet https://github.com/flutter/flutter/pull/193362
Skip to content

[web] Let the browser handle Control+wheel zoom outside of macOS - #193362

Open
diegolopezrm wants to merge 1 commit into
flutter:masterfrom
diegolopezrm:web-ctrl-wheel-zoom
Open

diegolopezrm wants to merge 1 commit into
flutter:masterfrom
diegolopezrm:web-ctrl-wheel-zoom

Conversation

@diegolopezrm

Copy link
Copy Markdown
Contributor

The problem

On Windows and Linux, holding Control and turning the mouse wheel zooms the page in every browser. In a Flutter web app it does nothing: the page does not zoom and the app does not react either. Control+Plus and Control+Minus still work, as the reporter noted, which makes the wheel feel broken rather than deliberately disabled.

Issue: #129933

Root cause

Browsers report a trackpad pinch as a wheel event with ctrlKey set. Since flutter-team-archive/engine#36348 (Flutter 3.10) the web engine turns every wheel event with ctrlKey into a PointerSignalKind.scale and calls preventDefault() on it, so that InteractiveViewer and friends can zoom on pinch. That is right for a pinch. But a user holding the Control key and turning the wheel produces exactly the same event, and gets swallowed with it.

Before that change _handleWheelEvent had this:

if (event.getModifierState('Control') &&
    operatingSystem != OperatingSystem.macOs &&
    operatingSystem != OperatingSystem.iOs) {
  // Ignore Control+wheel events since the default handler
  // will change browser zoom level instead of scrolling.
  return;
}

The macOS path already knows how to tell the two apart: it asks the keyboard converter whether a physical Control key is held, because a pinch never involves a key press. Windows and Linux never got that check, which is how they lost browser zoom.

The fix

_isBrowserZoomShortcut applies the same physical-key check on every platform except macOS and iOS. When the user is holding Control and turning the wheel, _handleWheelEvent returns before forwarding the event to the framework and without cancelling it, so the browser zooms the page the way it does on any other site. A pinch still reaches the framework as a scale signal and is still cancelled, so nothing changes for InteractiveViewer.

macOS keeps its current behaviour on purpose: Control+wheel does not zoom the browser there, and the existing code already reports it as a plain scroll.

Platform Event Before After
Windows, Linux Trackpad pinch (ctrlKey, no key held) scale, cancelled scale, cancelled
Windows, Linux Control held + mouse wheel scale, cancelled, no zoom left to the browser, page zooms
macOS Control held + wheel scroll scroll
macOS Trackpad pinch scale, cancelled scale, cancelled

The one behavioural change is for an app that relied on getting a PointerScaleEvent from a mouse wheel with Control held on Windows or Linux. That never worked on macOS, did not work before 3.10, and it competes with the browser's own shortcut, so restoring the pre-3.10 behaviour looks like the right call to me. If the team would rather keep delivering the event and only skip preventDefault(), I am happy to change it.

Testing

  • New test leaves Control+wheel to the browser outside of macOS in pointer_binding_test.dart covers Windows and Linux: a pinch is delivered as a scale and cancelled, a wheel with Control physically held is neither delivered nor cancelled, and releasing the key brings scrolling back. felt test --gcs-prod --compiler dart2js test/engine/pointer_binding_test.dart passes (73 tests), and I checked that the new test fails without the fix.
  • The existing does choose scroll vs scale based on ctrlKey test still passes, so the macOS path is unchanged.
  • I do not have a Windows or Linux machine at hand, so I have not exercised this in a real browser yet. The change is small and the unit test pins the exact event flow, but if a reviewer can give it a spin with a mouse on Windows Chrome or Edge, that would be a welcome sanity check.

Pre-launch Checklist

Notes on the checklist: the only behaviour that changes is described in the table above, and it restores what the engine did before 3.10. There is no API change, so no Data Driven Fixes apply.

Browsers report trackpad pinch gestures as wheel events with ctrlKey set,
and since flutter-team-archive/engine#36348 every such event is turned into a scale
signal and cancelled. That also swallows the user holding the Control key
and turning the mouse wheel, which is the browser's zoom shortcut on
Windows, Linux and ChromeOS, so those pages could no longer be zoomed.

Tell the two apart the same way the macOS code path already does: a pinch
never involves a physical Control key press. When the key is held, leave
the event to the browser instead of forwarding or cancelling it.

Fixes flutter#129933
@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team labels Sep 25, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the web engine's pointer binding to identify browser zoom shortcuts (Control+wheel) on platforms outside of macOS and iOS, allowing the browser to handle the zoom instead of forwarding the event to the framework. A test is added to verify this behavior. The review feedback suggests resetting ui.PlatformDispatcher.instance.onPointerDataPacket to null at the end of the new test to prevent test pollution.

Comment thread engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart
@flutter-zl
flutter-zl requested a review from mdebbar October 7, 2026 18:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine flutter/engine related. See also e: labels. platform-web Web applications specifically team-web Owned by Web platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant