Issue #157435: Touch Scroll Not Propagating to Host Page (Embedded Mode)

Problem Description

When a Flutter web app is embedded in a host page using multi-view/custom element embedding (not iframe), touch scrolling inside the Flutter view does not propagate to the host page when the Flutter content reaches its scroll boundary.

User Experience Impact

  • On mobile devices, users cannot scroll the host page by swiping on the Flutter embedded view
  • Touch scrolling feels “stuck” at Flutter content boundaries
  • Poor mobile UX for embedded Flutter content

Difference from Issue #156985

  • Issue #156985 is about mouse wheel scrolling in iframe embedding
  • Issue #157435 is about touch scrolling in custom element embedding
  • Both require scroll propagation to parent, but through different mechanisms

Root Cause Analysis

  1. touch-action: none: Flutter sets touch-action: none on the host element to ensure Flutter receives all touch events for gesture recognition.

  2. preventDefault() on pointerdown: The engine was calling preventDefault() on ALL pointerdown events, which prevents the browser from initiating native touch scrolling.

  3. No touch scroll propagation mechanism: Unlike wheel events, there was no way for Flutter to propagate touch scroll deltas to the host page when at boundary.

Solution

Engine Changes (pointer_binding.dart)

Don't preventDefault() for touch events on pointerdown:

// In pointerdown handler
if (event.pointerType != 'touch') {
  event.preventDefault();
}
// Touch events: let browser handle scroll initiation

This allows the browser's touch scrolling to work, while Flutter still handles touch events first via touch-action: none.

Framework Changes (scroll_position_with_single_context.dart)

Added touch scroll propagation via platform channel:

static const BasicMessageChannel<Object?> _scrollChannel = 
    BasicMessageChannel<Object?>('flutter/scroll', JSONMessageCodec());

@override
void applyUserOffset(double delta) {
  // Check if at boundary before applying scroll
  final bool wasAtMin = pixels <= minScrollExtent;
  final bool wasAtMax = pixels >= maxScrollExtent;
  
  // Apply the scroll
  setPixels(pixels - physics.applyPhysicsToUserOffset(this, delta));
  
  // On web, propagate to parent when at boundary
  if (kIsWeb) {
    final bool shouldPropagateDown = delta < 0 && (wasAtMax || pixels >= maxScrollExtent);
    final bool shouldPropagateUp = delta > 0 && (wasAtMin || pixels <= minScrollExtent);
    
    if (shouldPropagateDown || shouldPropagateUp) {
      _propagateOverscrollToParent(delta);
    }
  }
}

Engine Changes (platform_dispatcher.dart)

Added handler for flutter/scroll platform channel:

case 'flutter/scroll':
  final dynamic decoded = messageCodec.decodeMessage(data);
  if (decoded is Map) {
    final double deltaX = (decoded['deltaX'] as num?)?.toDouble() ?? 0.0;
    final double deltaY = (decoded['deltaY'] as num?)?.toDouble() ?? 0.0;
    scrollParentWindow(deltaX, deltaY);
    replyToPlatformMessage(callback, messageCodec.encodeMessage(true));
  }
  return;

CSS Changes (Embedding Strategies)

Added overscroll-behavior: contain to prevent any residual scroll chaining:

// In custom_element_embedding_strategy.dart & full_page_embedding_strategy.dart
setElementStyle(rootElement, 'overscroll-behavior', 'contain');

Files Changed

FileChange
engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dartDon't preventDefault on touch pointerdown
packages/flutter/lib/src/widgets/scroll_position_with_single_context.dartTouch scroll propagation via platform channel
engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dartflutter/scroll channel handler
engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/*.dartoverscroll-behavior: contain CSS

Demo

Behavior After Fix

  1. ✅ Touch scrolling inside Flutter embedded view works normally
  2. ✅ When Flutter content is at boundary, continued swiping scrolls the host page
  3. ✅ Flutter gesture recognition still works (tap, drag, etc.)
  4. ✅ Works for both vertical and horizontal scrolling