This directory documents the fixes for three related GitHub issues dealing with scroll event handling in Flutter web applications.
| Issue | Problem | Key Fix |
|---|---|---|
| #156985 | Scroll events bubble to parent page when Flutter is in iframe | preventDefault() in iframe + explicit parent scroll via postMessage |
| #157435 | Touch scroll doesn't propagate to host page (embedded mode) | Don't preventDefault() on touch + platform channel for scroll propagation |
| #113196 | Mouse scroll blocked over cross-origin iframe in HtmlElementView | Transparent overlay captures wheel events and forwards to Flutter |
┌─────────────────────────────────────────────────────────────────┐
│ Host/Parent Page │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Flutter Web App │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ Flutter Scrollables │ │ │
│ │ │ ┌───────────────────────────────────────────────┐ │ │ │
│ │ │ │ HtmlElementView (cross-origin iframe) │ │ │ │
│ │ │ │ + Wheel Overlay (Issue #113196) │ │ │ │
│ │ │ └───────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ pointer_binding.dart: │ │
│ │ - Detects iframe embedding (Issue #156985) │ │
│ │ - preventDefault() to block native scroll chaining │ │
│ │ - Explicit parent scroll via postMessage │ │
│ │ - Touch event handling (Issue #157435) │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ postMessage('flutter-scroll') │
│ ▼ │
│ window.addEventListener('message', scrollBy) │
└─────────────────────────────────────────────────────────────────┘
engine/src/flutter/lib/web_ui/lib/src/engine/)| File | Changes |
|---|---|
pointer_binding.dart | Iframe detection, preventDefault in iframe, touch event handling, parent scroll |
dom.dart | scrollParentWindow() function, parent property on DomWindow |
platform_dispatcher.dart | flutter/scroll platform channel handler |
platform_views/content_manager.dart | Wheel overlay for HtmlElementView |
view_embedder/embedding_strategy/*.dart | overscroll-behavior: contain CSS |
packages/flutter/lib/src/widgets/)| File | Changes |
|---|---|
scrollable.dart | respond(allowPlatformDefault: false) when scroll handled |
scroll_position_with_single_context.dart | Touch scroll propagation via platform channel |
For iframe embedding (Issue #156985), the host page must listen for scroll messages:
<script> window.addEventListener('message', function(event) { if (event.data && event.data.type === 'flutter-scroll') { window.scrollBy(event.data.deltaX, event.data.deltaY); } }); </script>
# Build engine with changes cd /Users/zhongliu/dev/flutter/engine/src/flutter/lib/web_ui felt build # Test demo app cd /Users/zhongliu/dev/flutter-apps/issue_156985_after flutter run -d chrome --local-web-sdk=wasm_release # Verify: # 1. Scroll inside Flutter - parent page should NOT scroll # 2. Scroll to boundary - parent page SHOULD scroll # 3. Nested scrollables - inner scrolls first, then outer, then parent
postMessage for cross-origin safety: Using postMessage instead of direct window.parent.scrollBy() ensures the solution works for both same-origin and cross-origin iframes.
Two-flag system for nested scrollables: The _lastWheelEventAllowedDefault and _lastWheelEventHandledByWidget flags work together to ensure correct behavior with nested scrollables.
Overlay for cross-origin iframes: Since cross-origin iframes completely isolate events, an overlay is the only way to capture wheel events without breaking iframe functionality.
Don't block touch events: Touch scrolling uses browser native behavior, so we don't preventDefault() on touch to allow smooth scrolling experience.