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.
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.
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.
No Event Forwarding: Unlike same-origin iframes, cross-origin iframes cannot forward events to the parent due to the Same-Origin Policy.
Add a transparent overlay element on top of the platform view that captures wheel events and forwards them to Flutter:
content_manager.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; }); }
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', <String, dynamic>{ '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); } }), <String, bool>{'capture': false, 'passive': false}.jsify()!, ); }
For clicks and other pointer events, temporarily disable the overlay:
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<void>.microtask(() { overlay.style.pointerEvents = originalPointerEvents; }); }
| File | Change |
|---|---|
engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart | Wheel overlay, event forwarding, click-through |
| 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 |
pointer-events: none on iframe: Would block all iframe interactionThe overlay approach is the best balance of scroll functionality and iframe interactivity.