# Issue #113196: Mouse Scroll Blocked Over HtmlElementView/Cross-Origin iframe ## Problem Description When using `HtmlElementView` to embed a cross-origin iframe (e.g., YouTube video, Google Maps, third-party content) inside a Flutter web app, mouse wheel scrolling over the iframe is completely blocked. The user cannot scroll the Flutter page while hovering over the embedded iframe. ### User Experience Impact - Users cannot scroll the page when mouse is over embedded content - Have to move mouse away from iframe to scroll - Makes pages with large embedded iframes very difficult to navigate ### Difference from Other Issues - Issue #156985: Flutter IN an iframe, scroll bubbling UP to parent - Issue #157435: Touch scroll in embedded mode - Issue #113196: Scroll OVER a cross-origin iframe INSIDE Flutter ## Root Cause Analysis 1. **Cross-Origin Isolation**: Cross-origin iframes completely isolate all events due to browser security. Wheel events that occur inside the iframe never reach the parent Flutter page. 2. **Browser Security Model**: When the mouse is over a cross-origin iframe, the browser sends wheel events to the iframe's document, not the parent. The parent document receives nothing. 3. **No Event Forwarding**: Unlike same-origin iframes, cross-origin iframes cannot forward events to the parent due to the Same-Origin Policy. ## Solution ### Transparent Overlay Approach Add a transparent overlay element on top of the platform view that captures wheel events and forwards them to Flutter: ### Engine Changes (`content_manager.dart`) ```dart DomElement _safelyCreatePlatformViewSlot(int viewId, String viewType, String slotName) { return _contents.putIfAbsent(viewId, () { final DomElement wrapper = domDocument.createElement('flt-platform-view') ..id = getPlatformViewDomId(viewId) ..setAttribute('slot', slotName) ..style.position = 'relative' ..style.width = '100%' ..style.height = '100%' ..style.display = 'block'; // ... create content ... // Add transparent overlay to capture wheel events final DomElement wheelOverlay = domDocument.createElement('div') ..style.position = 'absolute' ..style.top = '0' ..style.left = '0' ..style.width = '100%' ..style.height = '100%' ..style.zIndex = '1000' ..style.pointerEvents = 'auto'; _setupWheelEventForwarding(wheelOverlay, wrapper); wrapper.append(wheelOverlay); return wrapper; }); } ``` ### Wheel Event Forwarding ```dart void _setupWheelEventForwarding(DomElement overlay, DomElement wrapper) { overlay.addEventListener( 'wheel', createDomEventListener((DomEvent event) { event.stopPropagation(); event.preventDefault(); // Find flutter-view element DomElement? flutterView = wrapper.parentElement; while (flutterView != null && flutterView.tagName != 'FLUTTER-VIEW') { flutterView = flutterView.parentElement; } if (flutterView != null) { // Create and dispatch new wheel event to flutter-view final DomWheelEvent wheelEvent = event as DomWheelEvent; final DomWheelEvent newEvent = createDomWheelEvent( 'wheel', { 'bubbles': true, 'cancelable': true, 'clientX': wheelEvent.clientX, 'clientY': wheelEvent.clientY, 'deltaX': wheelEvent.deltaX, 'deltaY': wheelEvent.deltaY, 'deltaMode': wheelEvent.deltaMode, 'buttons': wheelEvent.buttons, }, ); flutterView.dispatchEvent(newEvent); } }), {'capture': false, 'passive': false}.jsify()!, ); } ``` ### Click-Through Handling For clicks and other pointer events, temporarily disable the overlay: ```dart void _forwardPointerEventToContent(DomMouseEvent event, DomElement overlay) { // Temporarily hide overlay to allow click-through final String originalPointerEvents = overlay.style.pointerEvents; overlay.style.pointerEvents = 'none'; // Use microtask to restore after browser dispatches event Future.microtask(() { overlay.style.pointerEvents = originalPointerEvents; }); } ``` ## Files Changed | File | Change | |------|--------| | `engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart` | Wheel overlay, event forwarding, click-through | ## Trade-offs | Aspect | Impact | |--------|--------| | Wheel scrolling | ✅ Works - overlay captures and forwards to Flutter | | Click/tap on iframe | ✅ Works - overlay temporarily disables for clicks | | Iframe interactivity | ⚠️ Limited - complex interactions inside iframe may not work | | Keyboard input | ✅ Works - overlay doesn't capture keyboard events | ## Demo - **Before Fix**: https://issue-113196-before.web.app - **After Fix**: https://issue-113196-after.web.app ## Behavior After Fix 1. ✅ Mouse wheel scrolling over cross-origin iframes scrolls the Flutter page 2. ✅ Clicking on the iframe content still works (video play, map interaction) 3. ✅ Flutter scrollables above/below the iframe work normally 4. ⚠️ Some complex iframe interactions may require clicking first to "focus" the iframe ## Alternative Approaches Considered 1. **CSS `pointer-events: none` on iframe**: Would block all iframe interaction 2. **iframe sandbox**: Would break iframe functionality 3. **postMessage coordination**: Requires cooperation from iframe content (not possible for third-party) The overlay approach is the best balance of scroll functionality and iframe interactivity.