2025-12-01 09:26:03 -08:00

4.3 KiB

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

File Change
engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart Don't preventDefault on touch pointerdown
packages/flutter/lib/src/widgets/scroll_position_with_single_context.dart Touch scroll propagation via platform channel
engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart flutter/scroll channel handler
engine/src/flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/*.dart overscroll-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