Skip to content

Wrong scene composition (Canvaskit) when first embedded view is invisible (e.g. using PointerInterceptor) #118452

@lclaesen

Description

@lclaesen

Steps to Reproduce

  1. Execute flutter run --web-renderer canvaskit -d chrome on the code sample
  2. Verify that the actual and expected results (as described below) differ
  3. In the sample code: comment out the ìnsertion of the Stack item const HtmlElementView(viewType: invisibleDivType) and run the sample app again
  4. Verify that the result is as expected.

Expected results: The drawer-like overlay (just a Stack item positioned to the left) should display an orange box (an HtmlElement with a simple html div colored orange).

Actual results: The HtmlElement is not shown; it is actually hidden / covered by the a canvas element on which most of the
used framework widgets are drawn (i.e. the drawer like container and the app scaffold itself).

Please note that this issue only occurs when using the canvaskit renderer for the web.

What seems to go wrong

What seems to happen here is that canvaskit\embedded_views.dart does something wrong when the first embedded view it encounters is an 'invisible' one (by which I mean: a HtmlElementView created by a viewFactory registered with isVisible: false). This results in later HtmlElementViews that are visible not being correctly layered. As one of the attached screenshots shows, the platform view with the visible html is obfuscated by a canvas that draws what should be underneath it; i.e. the emitted scene structure is:

  • flt-scene
    • flt-canvas-container: on which nothing is drawn
    • flt-platform-view-slot: for the invisible HtmlElementView
    • flt-platform-view-slot: for the visible HtmlElementView
    • flt-canvas-container: on which the scaffold and drawer are drawn

This is not a contrived situation, since PointerInterceptor does exactly that: it inserts an invisible HtmlElementView in order to ensure widgets remain responsive, even when overlayed over, let's say, an embedded iframe. Because we want drawers, modals, overlay entries, ... to be responsive irrespective of what's underneath them, we wrap them in a PointerInterceptor. But because of this issue, PointerInterceptor also disallows its child to embed a visible HtmlElement itself -- at least when there is no visible HtmlElement below it.

Otherwise put: we expect that our overlay entries can overlay embedded html view (and stay responsive); but we also expect that our overlay entries can embed html. And because of the current issue, we don't seem to be able to have both.

Code sample
// ignore_for_file: avoid_web_libraries_in_flutter, undefined_prefixed_name, avoid_dynamic_calls
import 'dart:html' as html;
import 'dart:ui' as ui;
import 'package:flutter/material.dart';

const visibleDivType = 'visibleDiv';
const invisibleDivType = 'invisibleDiv';
const title = 'Flutter issue demo';

void main() {
  ui.platformViewRegistry.registerViewFactory(
      visibleDivType,
      (int viewId) => html.DivElement()
        ..style.border = 'none'
        ..style.height = '100%'
        ..style.width = '100%'
        ..style.backgroundColor = 'orange'
        ..appendText('An embedded div'),
      isVisible: true);
  ui.platformViewRegistry.registerViewFactory(
      invisibleDivType,
      (int viewId) => html.DivElement()
        ..style.height = '100%'
        ..style.width = '100%'
        ..style.backgroundColor = 'green',
      isVisible: false);
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: title,
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(title: const Text(title)),
          body: const Home(),
        ));
  }
}

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        const Center(),
        // Please comment out in order to verify that it is this
        // invisible HtmlElementView that causes the wrong layering:
        const HtmlElementView(viewType: invisibleDivType),
        Positioned(
            child: SizedBox(
          width: 400,
          child: Container(
              color: Colors.white,
              child: Column(
                mainAxisSize: MainAxisSize.max,
                children: const [
                  Text(
                      '''A dummy drawer or any other type of overlay. One would expect to see an orange div appear below.'''),
                  SizedBox(
                      width: 100,
                      height: 40,
                      child: HtmlElementView(viewType: 'visibleDiv')),
                ],
              )),
        ))
      ],
    );
  }
}

Screenshots

The actual result:

actual result

The expected result after commenting out the invisible Html element:

expected result after leaving out invisible Html element

The flt-scene with top canvas hidden:

flt-scene with top canvas hidden

Metadata

Metadata

Assignees

Labels

P0Critical issues such as a build break or regressioncustomer: dente: web_canvaskitCanvasKit (a.k.a. Skia-on-WebGL) rendering backend for Webengineflutter/engine related. See also e: labels.found in release: 3.3Found to occur in 3.3found in release: 3.7Found to occur in 3.7has reproducible stepsThe issue has been confirmed reproducible and is ready to work onplatform-webWeb applications specificallyr: fixedIssue is closed as already fixed in a newer version

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions