MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2
https://aka.ms/webview2
427 stars 51 forks source link

Switching between single screen and multi screen RDP on high DPI causes black window and unresponsive machine #1212

Open nabils opened 3 years ago

nabils commented 3 years ago

Description When opening some WPF apps (each containing single instance WebView component) on one machine (single screen) and then connecting from another machine (dual screen) over RDP in high DPI, when launching a new instance it results in a black window and the whole machine becomes unresponsive (dragging windows visual artefacts etc.).

Version SDK: 1.0.774.44 Runtime: Edge 89 Framework: WPF OS: Win10

Repro Steps I can repro easily every time using mac OSX remote desktop. 1) Connect to windows machine with OSX RDP (single screen) with "Optimize for Retina Displays" turned on. 2) Open a number of instances of a WPF app each one navigated to different url. 3) Disconnect from RDP and reconnect to same machine from dual screen setup ("Optimize for Retina Displays" turned on) 4) Launch a new instance of the WPF app the window is black and whole machine becomes unresponsive (dragging windows visual artefacts etc.) until that process is killed.

n.b. Does not happen with "Optimize for Retina Displays" turned off (i.e. standard DPI).

A far from ideal workaround I have at the moment is to disable dpiAware in WPF manifest but results in a blurry view and obviously cannot get high DPI support.

AB#32898337

champnic commented 3 years ago

Thanks for the bug report @nabils, I've opened it on our backlog.

cbra-caa commented 3 years ago

We have experienced a similar issue for users with laptops connecting and disconnecting an additional screen. Only windows containing a webview is unresponsive in our case. All other windows from the same, and other, apps functions as expected.

@champnic We can replicate it in a sample app, using only a single monitor and no Remote Desktop, only by changing zoom level for the screen. Is that something you need, or is the issue sufficiently documented?

champnic commented 3 years ago

@cbra-caa This is helpful. By "zoom level for the screen" I assume you are referring to this scale setting in Settings? image

I just tried in a simple WinForms app and didn't see this happen. What framework are you using? Do you have any DPI related handling or setting in your app?

cbra-caa commented 3 years ago

Yep, just exactly that setting.

The app catches the WM_DISPLAYCHANGE and then disposes the webview. After a set amount of time (3 seconds) the webview is rebuild and then the weird behavior is observable.

The problem occurs for all zoom levels different than the one chosen, when the app is started. F.ex. start at 125% change to 150% the problem is visible. Change back to 125%, everything is fine. Down to 100%, the problem arises again. For one of my colleagues he needed to restart his pc once, before the behavior occured.

Setup: Windows: 10 Nuget: 1.0.864.35 Edge: 93.0.956.0 canary

cbra-caa commented 3 years ago

MainWindow.xaml

<Window x:Class="ZoomIssueApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="800" Width="1280">

  <Window.Resources>
    <Style TargetType="{x:Type CheckBox}">
      <Setter Property="HorizontalAlignment" Value="Left" />
      <Setter Property="VerticalContentAlignment" Value="Center" />
      <Setter Property="Foreground" Value="White" />
      <Setter Property="FontSize" Value="12" />
      <Setter Property="Margin" Value="0 0 0 8" />
    </Style>

    <Style TargetType="{x:Type TextBox}">
      <Setter Property="Foreground" Value="White" />
      <Setter Property="BorderBrush" Value="White" />
      <Setter Property="Background" Value="Black" />
    </Style>
  </Window.Resources>

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <Grid Background="Black"
          Height="170">
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
        <ColumnDefinition Width="*" />
      </Grid.ColumnDefinitions>

      <StackPanel Margin="16 16 16 16">
        <CheckBox x:Name="HideWebView"
                  Content="Hide webview on resolution changed" />

        <StackPanel Orientation="Horizontal">
          <Label Content="Loading delay in ms:"
                 Foreground="White"
                 Margin="0 0 8 8" />

          <TextBox x:Name="LoadingDelay"
                   Height="21"
                   Text="3000" />
        </StackPanel>
      </StackPanel>

      <ScrollViewer x:Name="TextScroller" 
                    Grid.Column="1"
                    HorizontalAlignment="Right">
        <TextBox x:Name="LogBox"
                 IsReadOnly="True"
                 TextWrapping="Wrap" />
      </ScrollViewer>
    </Grid>

    <ContentPresenter x:Name="WebViewPresenter"
                      Grid.Row="1" />

    <Border x:Name="LoadingBorder"
            Grid.Row="1"
            Background="White">

      <TextBlock HorizontalAlignment="Center"
                 VerticalAlignment="Center"
                 Text="WebView reloading!"
                 FontSize="24" />

    </Border>

  </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;

namespace ZoomIssueApp
{
  public partial class MainWindow : Window
  {
    private WebView2 WebView { get; set; }
    private StringBuilder Logger { get; } = new StringBuilder();
    private Uri LastAddres { get; set; } = new Uri("https://www.microsoft.com");

    public MainWindow()
    {
      InitializeComponent();

      Loaded += MainWindow_Loaded;
      SourceInitialized += MainWindow_SourceInitialized;
    }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
      StringBuilderExtension.Displayer = LogBox;
      StringBuilderExtension.Scroller = TextScroller;

      LoadWebView();
    }

    private void MainWindow_SourceInitialized(object sender, EventArgs e)
    {
      IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
      HwndSource src = HwndSource.FromHwnd(windowHandle);
      src.AddHook(new HwndSourceHook(WndProc));
    }

    #region Load / Dispose

    private void LoadWebView()
    {
      WebView = new WebView2();
      WebView.CoreWebView2InitializationCompleted += WebView_CoreWebView2InitializationCompleted;

      WebViewPresenter.Content = WebView;

      InitializeAsync();
    }

    private async void InitializeAsync()
    {
      Environment.SetEnvironmentVariable("WEBVIEW2_RELEASE_CHANNEL_PREFERENCE", "1");

      var environment = await CoreWebView2Environment.CreateAsync();
      EnsureAsync(environment);
    }

    private async void EnsureAsync(CoreWebView2Environment environment)
    {
      try
      {
        await WebView.EnsureCoreWebView2Async(environment);
      }
      catch (Exception exception)
      {
        Logger.AddLogLine("WebView failed on initialization. Seen when the control gets unloaded before the WebView has finished its async construction - but can be safely ignored.");
        Logger.AddLogLine(exception.ToString());
      }

      WebView.Source = LastAddres;
    }

    private void WebView_CoreWebView2InitializationCompleted(object sender, CoreWebView2InitializationCompletedEventArgs e)
    {
      Logger.AddLogLine($"WebView nuget version is {Assembly.GetAssembly(typeof(WebView2)).GetName().Version}");
      Logger.WriteLog($"WebView runtime is {WebView.CoreWebView2.Environment.BrowserVersionString}");

      LoadingBorder.Visibility = Visibility.Collapsed;
    }

    private void DisposeWebView()
    {
      LastAddres = WebView.Source;
      WebView.Dispose();
      WebView = null;
      WebViewPresenter.Content = null;

      LoadingBorder.Visibility = Visibility.Visible;
    }

    #endregion

    #region HwndProc

    private const int WM_DISPLAYCHANGE = 0x007E;

    private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
      if (!IsLoaded || WebView == null)
        return IntPtr.Zero;

      if (msg == WM_DISPLAYCHANGE && HideWebView.IsChecked == true)
      {
        Logger.AddLogLine($"WM_DISPLAYCHANGE caugt - hide the webview !");
        DisposeWebView();

        int loadingDelay = 3000;
        int.TryParse(LoadingDelay.Text, out loadingDelay);

        Logger.WriteLog($"Reloading webview with a {loadingDelay} ms delay");
        Task.Run(async delegate
        {
          await Task.Delay(loadingDelay);

          Dispatcher.Invoke(LoadWebView);
        });
      }

      return IntPtr.Zero;
    }

    #endregion
  }

  public static class StringBuilderExtension
  {
    public static TextBox Displayer { get; set; }
    public static ScrollViewer Scroller { get; set; }

    public static StringBuilder AddLogLine(this StringBuilder sb, string text)
      => sb.AppendLine($"{DateTime.Now:HH:mm:ss} // " + text);

    public static void WriteLog(this StringBuilder sb)
    {
      sb.AppendLine();
      Displayer.Text = sb.ToString();
      Scroller.ScrollToEnd();
    }

    public static void WriteLog(this StringBuilder sb, string text)
      => sb.AddLogLine(text).WriteLog();
  }
}
champnic commented 3 years ago

Aha - my guess is this is not a problem with DPI changing, but rather an issue with disposing and then creating a new WebView2 right away. It's likely that the browser processes have already begun shutting down from the dispose call, and then get stuck when you try to create a new WebView2.

You might try creating a new WebView2 first, and then disposing the old one. This should keep the necessary browser processes alive.

cbra-caa commented 3 years ago

@champnic That might be the case. Though if that is the case, why does the 'crash' only occur for changes to the DPI that are different from the level when the app was opened? I tried to make a screen recording, but the one I currently have stops recording on DPI changed, so I got you some screenshots instead.

The same behavior shown below can be replicated with any start zoom, and any other dpi values following that to create, and undo, the crash.

OBS: All of these shots were run with a delay of 3 seconds from disposing the webview to reloading of it. But I can replicate it with both a 0 second delay, and a 10 second delay as well.

App opened, initial zoom shown (125%):

Step1

Check-mark set to activate behavior, zoom changed (150%):

Step2

Loading has finished, app is broken:

Step3

Zoom changed back (125%), app fixed:

Step4

champnic commented 3 years ago

I could imagine in your test that it broke when going from 125 -> 150 due to the timing issue, but then the WV2 finished cleaning up when you went back to 150 -> 125 and so it worked. I'd be interested if you ran your scenarios without a DPI change (like in response to a button click or timer in the app instead), and if you did 125 -> 150, pause, 150 -> 175 instead to see if my hypothesis is correct.

cbra-caa commented 3 years ago

A colleague recommended me another dpi-independent video recorder, aka. my phone, so now you can experience both the bug, and my very mediocre dual-wielding abilities. 😅

The video is too large to attach, is there another way to get it to you?

champnic commented 3 years ago

If you upload somewhere like Google Drive or Dropbox and then share a link? If you want to keep it private you can email me the link at champnic@microsoft.com :)

cbra-caa commented 3 years ago

@champnic I sent you an email with a drive-link ;)

champnic commented 3 years ago

I've reviewed the video, thanks for sending! Seems my hypothesis is disproven, and it does seem to be tied to a specific DPI. What happens if you don't tear-down/relaunch WebView2 when the DPI changes? In theory WebView2 should respond to DPI changes correctly (or be set to be DPI aware).

cbra-caa commented 3 years ago

If I dont use the 'Hide WebView on Resolution Change' function the WebView acts as expected and responds correctly to the DPI change 98% of the time. The harder-to-hit problem, aka the last 2%, was found when a couple of users experienced the same behavior when detaching their laptop from their auxiliary screen. The second screen has 100% zoom and the laptop screen, to which the webview is forced after the disconnect is 125% zoom.

The above app which reproduces the problem was actually made as an attempt to avoid this problem under the assumption that the WebView would respond better to being instantiated in a new environment when the DPI changed than just responding itself.