Skip to content

[windows] CoreWebView2 renderer processes accumulate — one leaked per WebViewWidget mount #25

Description

@kwang87

Environment

   
webview_all 1.2.1
webview_all_windows 1.2.1
Flutter 3.44.7 (stable), Dart 3.12.2
OS Windows 11 Pro 26200 (x64)
WebView2 Runtime 150.0.4078.105

What I'm doing

I migrated a Flutter desktop app from webview_flutter to webview_all to get Windows support. The app shows a web page in a modal dialog for an OAuth sign-in flow: the dialog is opened, the user completes the flow, the dialog closes. This can happen several times per app session (e.g. the user cancels and retries).

The controller is created per dialog, using only the standard webview_all API:

import 'package:flutter/material.dart';
import 'package:webview_all/webview_all.dart';

void main() => runApp(const MaterialApp(home: Home()));

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

@override
Widget build(BuildContext context) => Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => showDialog(
context: context,
builder: (_) => const WebViewDialog(),
),
child: const Text('open webview dialog'),
),
),
);
}

class WebViewDialog extends StatefulWidget {
const WebViewDialog({super.key});

@override
State<WebViewDialog> createState() => _WebViewDialogState();
}

class _WebViewDialogState extends State<WebViewDialog> {
late final WebViewController _controller;

@override
void initState() {
super.initState();
_controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..loadRequest(Uri.parse('https://example.com'));
}

@override
Widget build(BuildContext context) => Dialog(
child: SizedBox(
width: 520,
height: 700,
child: WebViewWidget(controller: _controller),
),
);
}

Steps to reproduce

  1. Run the app on Windows.
  2. Open the dialog, then close it. Repeat 5 times.
  3. With every dialog closed, count the app's WebView2 renderer processes. They can be attributed by the default user data dir (%LOCALAPPDATA%\webview_all_windows\<exe-stem>):
Get-CimInstance Win32_Process -Filter "Name='msedgewebview2.exe'" |
  Where-Object { $_.CommandLine -like '*webview_all_windows*' -and
                 $_.CommandLine -like '*--type=renderer*' } |
  Measure-Object | Select-Object -ExpandProperty Count

Expected

Renderer count drops back to 0 once the dialogs are closed and the WebViewWidgets are removed from the tree.

Actual

The renderer count equals the number of times the dialog was opened, and stays there until the process exits. After 5 open/close cycles:

Type              PID    WS_MB   Start
browser(root)     40468  150.9   11:03:48
gpu-process       40408  113.7   11:03:48
utility           40128   43.1   11:03:48
utility           40100   19.5   11:03:48
crashpad-handler   8896   14.0   11:03:48
renderer           8744   62.2   11:03:48   <- open #1
renderer          41668   63.7   11:03:54   <- open #2
renderer          41872   62.8   11:03:56   <- open #3
renderer          40352   62.7   11:03:58   <- open #4
renderer          41480   60.5   11:03:59   <- open #5

All five renderers are still alive with no dialog open — about 312 MB retained on top of the ~341 MB shared browser/GPU/utility processes. The browser root process is a child of the Flutter app process, and the user data dir confirms the attribution.

Each renderer's start time lines up with a dialog open, and none of them exit when the dialog closes.

On app exit, the Chromium module also logs:

[ERROR:ui\gfx\win\window_impl.cc:172] Failed to unregister class Chrome_WidgetWin_0. Error = 1412

(1412 = ERROR_CLASS_HAS_WINDOWS, i.e. windows of that class were still alive at teardown.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions