CefSharp icon indicating copy to clipboard operation
CefSharp copied to clipboard

WPF - Browser not correctly refreshing on Resize >= 127.3.50

Open oetjen opened this issue 1 year ago • 25 comments

Is there an existing issue for this?

  • [X] I have searched both open/closed issues, no issue already exists.

CefSharp Version

129.0.110

Operating System

Windows 11

Architecture

x64

.Net Version

.NET Framework 4.8

Implementation

WPF

Reproduction Steps

Create a WPF Window with a Grid, add several ChromiumWebBrowsers into each grid. Load some simple HTML content into each browser. Start App and resize the window -> WebBrowser is not resized according to the window / grid size changes -> When using the mouse scroll wheel, the browser size is updated correctly. This behaviour is observed first in 127.3.50. In 126.2.180.0, everything was ok

The following project contains some sample code, as well as a video: See: https://github.com/oetjen/ChromiumResizeProblem

Expected behavior

As in 126.2.180.0 -> Browser windows should resize

Actual behavior

Browser windows do not resize

Regression?

Worked in 126.2.180.0

Known Workarounds

none

Does this problem also occur in the CEF Sample Application

No

Other information

No response

oetjen avatar Oct 10 '24 10:10 oetjen

I also see wpf ChromiumWebBrowser resizing issues in my demo app for Baksteen.Blazor.CefSharp, .net 8.0. When enlarging the window the webview seems to automatically resize up to a certain size, but then it stops, and it will only suddenly resize when I start hovering the mouse over elements in the webview. I've tested this issue occurs both in 128.4.90 and 129.0.110.

Screenrecording: https://github.com/user-attachments/assets/ad6ec647-095e-48ff-badf-ee96e41cf2e7

jpmikkers avatar Oct 12 '24 09:10 jpmikkers

There's been some major upstream changes recently. See https://github.com/cefsharp/CefSharp/issues/4795 for more details.

There's a pretty high change this is an upsteram issue. There's only been minor changes to the WPF implementation over the last few versions.

Make sure you have an app.manifest with Win 10 compatibility set so GPU detection works correctly.

https://github.com/cefsharp/CefSharp/wiki/Quick-Start-For-MS-.Net-4.x#40-add-appmanifest-to-your-application

Resize Problem in WPF Grid since 127.3.50

Using version 127.3.50 can you please try setting the following option, in your App.xaml.cs is ane asy place.

c#

 var settings = new CefSettings();
settings .ChromeRuntime = false;
settings.CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "MyCefSharpApp\\Cache")

var initialized = Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);

Create a WPF Window with a Grid, add several ChromiumWebBrowsers into each grid.

Is a grid actually relevant? Or is it just multiple browsers displayed at the same time?

amaitland avatar Oct 20 '24 03:10 amaitland

When enlarging the window the webview seems to automatically resize up to a certain size, but then it stops, and it will only suddenly resize when I start hovering the mouse over elements in the webview. I've tested this issue occurs both in 128.4.90 and 129.0.110.

Based on this I don't think multiple browsers has anything to do with the issue.

amaitland avatar Oct 20 '24 04:10 amaitland

It would be helpful if someone can run a git bisect on the 127 branch and see which commit introduced the change in behavior.

amaitland avatar Oct 26 '24 00:10 amaitland

@amaitland resize issue started in d3953b87ac261b45f0e279ae4520e765fe032477

jpmikkers avatar Oct 27 '24 11:10 jpmikkers

FWIW, it seems that after this commit the call to browser.GetHost().WasResized(); in OnActualSizeChanged() no longer reliably results in the callback leading to IRenderWebBrowser.GetViewRect(). (tested using the example CefSharp.Wpf.Example.netcore)

Update: strangely enough, I cannot reproduce the resize problem in CefSharp.WinForms.Example.netcore 😕

jpmikkers avatar Oct 28 '24 10:10 jpmikkers

resize issue started in d3953b8

Thanks for confirming. Have to see what changes were made upstream.

Did some testing, I can reproduce part of the problem with cefclient, have opened https://github.com/chromiumembedded/cef/issues/3822

It'll still be worth debugging cefclient to see what's difference.

amaitland avatar Nov 01 '24 08:11 amaitland

Likely related https://www.magpcss.org/ceforum/viewtopic.php?f=6&t=20038#p56379

amaitland avatar Nov 08 '24 21:11 amaitland

Other rendering issue with CEF OSR https://github.com/chromiumembedded/cef/issues/3826

amaitland avatar Dec 13 '24 20:12 amaitland

any update on when this issue might be fixed?

amipatel08 avatar Feb 27 '25 23:02 amipatel08

@amipatel08 fyi the wpf.hwndhost control does not have the resize issue.

jpmikkers avatar Feb 28 '25 00:02 jpmikkers

Other rendering issue with CEF OSR chromiumembedded/cef#3826

This issue needs to be fixed in CEF, you can subscribe there for updates.

You can use CefSharp.Wpf.HwndHost.ChromiumWebBrowser as an alternative (air space issues apply).

amaitland avatar Feb 28 '25 22:02 amaitland

https://github.com/chromiumembedded/cef/issues/3826#issuecomment-2685865905 suggests that explicitly calling Invalidate will get the browser to start redrawing again. Example would look something like:

// Gets a warpper around the CefBrowserHost instance
// You can perform a lot of low level browser operations using this interface
var cefbrowserHost = browser.GetBrowserHost();                

//You can call Invalidate to redraw/refresh the image
cefbrowserHost.Invalidate(PaintElementType.View);

I haven't tried this yet.

amaitland avatar Mar 01 '25 21:03 amaitland

I generally have what I would describe as glitchy resizing when running the Minimal WPF Example and I assume it is the same issue. Sometimes when resizing the window, the content stops re-rendering until I hover over an interactable element in the page.

I tried adding the following to my main window and it seems to mostly fix the issue with the page not re-rendering at all, though the resizing is still very far from smooth.

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    base.OnRenderSizeChanged(sizeInfo);

    Browser.GetBrowserHost()?.Invalidate(PaintElementType.View);
}

MortenChristiansen avatar Jun 14 '25 20:06 MortenChristiansen

@amaitland, @MortenChristiansen thanks! I've found it slightly more reliable (but obviously not pretty) by inserting a small delay.

    protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
    {
        base.OnRenderSizeChanged(sizeInfo);

        using var bridge = AsyncBridge.AsyncHelper.Wait; // https://github.com/tejacques/AsyncBridge
        bridge.Run(paintWithNewSize(browserHost));

        async static Task paintWithNewSize(IBrowserHost browserHost)
        {
            await Task.Delay(100);

            browserHost.Invalidate(PaintElementType.View);
        }
    }

(However, this still doesn't work 100% of the time, perhaps because it still fires too early. So I ultimately went with downgrading for now.)

chucker avatar Jun 23 '25 11:06 chucker

@MortenChristiansen @chucker Thanks for sharing! Perhaps we can add a check in to validate the size of the bitmap is the expected size and ask to generate a new frame if it's not.

amaitland avatar Jun 25 '25 08:06 amaitland

I refined my redrawing logic a bit more - it might be overkill, but here it is. The motivation for this was to avoid having a large amount of redraws when you resize the window. My application uses multiple individual browsers to render different parts of the window, so this increases the potential number of redraws.

As @chucker observed, a 100ms delay seems ideal, but in an attempt to smooth it out, I schedule 3 redraws with a 50ms delay between them. I'm not sure how much of a difference it is, but I think it makes it a bit smoother. You can always experiment with adding even finer grained updates than 50ms intervals.

The use of ConcurrentDictionary<BaseBrowser, byte> is just for lack of a ConcurrentSet<T> in .net.

I wrote this code before seeing the suggestion by @amaitland - maybe such a check would make my attempted optimization pointless, though the idea of trying to rerender a number of times in succession is still relevant regardless.

public abstract class BaseBrowser : ChromiumWebBrowser
{
    private static readonly ConcurrentDictionary<BaseBrowser, byte> _browsersScheduledForRedraw = [];

    static BaseBrowser()
    {
        Task.Run(async () =>
        {
            while (true)
            {
                var browsers = _browsersScheduledForRedraw.ToArray();
                _browsersScheduledForRedraw.Clear();

                foreach (var browser in browsers)
                    browser.Key.Redraw();

                await Task.Delay(50);

                foreach (var browser in browsers)
                    browser.Key.Redraw();

                await Task.Delay(50);

                foreach (var browser in browsers)
                    browser.Key.Redraw();
            }
        });
    }

    protected BaseBrowser()
    {
        SizeChanged += (sender, e) => _browsersScheduledForRedraw.AddOrUpdate(this, 0, (_, _) => 0);
    }

    private void Redraw()
    {
        this.GetBrowserHost()?.Invalidate(PaintElementType.View);
    }
}

MortenChristiansen avatar Jun 26 '25 15:06 MortenChristiansen

Would ConcurrentBag work here? Seems you don’t need uniqueness or key access, just iteration.

chucker avatar Jun 26 '25 17:06 chucker

The uniqueness aspect ensures there are no redundant redraws, so I think it is better to use the dictionary.

MortenChristiansen avatar Jun 27 '25 15:06 MortenChristiansen

Got a lot of funny hacks in this thread. The correct way is to set rendering for best performance.

var BaseSettings = new CefSettings();
BaseSettings.SetOffScreenRenderingBestPerformanceArgs(); //Fixes window resizing issue
Cef.Initialize(BaseSettings);

//Window components need to initialize after our settings
InitializeComponent();

KyBamboo avatar Jun 30 '25 15:06 KyBamboo

@KyBamboo Thanks for sharing.

Got a lot of funny hacks in this thread. The correct way is to set rendering for best performance.

Interesting this works. Not sure that I'd call it correct. This will disable GPU acceleration and GPU compositing.

Give the issue is with redrawing on resize, it might be worth trying just disabling GPU compositing.

cefSettings.CefCommandLineArgs.Add("disable-gpu-compositing");

amaitland avatar Jul 01 '25 09:07 amaitland

Which might explain this:

Update: strangely enough, I cannot reproduce the resize problem in CefSharp.WinForms.Example.netcore

WinForms (GDI+) doesn't have GPU acceleration, so perhaps CefSharp.WinForms is always non-accelerated, hence not triggering this bug.

chucker avatar Jul 01 '25 14:07 chucker

WinForms (GDI+) doesn't have GPU acceleration, so perhaps CefSharp.WinForms is always non-accelerated, hence not triggering this bug.

The WinForms version does support GPU Acceleration at the Chromium level. It's rendered differently, it's a native Win32 control. The equivalent is CefSharp.Wpf.HwndHost which is a native Win32 implementation using HwndHost.

WPF uses OSR rendering which is a CEF specific feature. Read more at https://github.com/cefsharp/CefSharp/wiki/General-Usage#offscreen-rendering-osr

amaitland avatar Jul 01 '25 20:07 amaitland

Give the issue is with redrawing on resize, it might be worth trying just disabling GPU compositing.

cefSettings.CefCommandLineArgs.Add("disable-gpu-compositing");

This appears to resolve the rendering issues, so we'll disable by default until the upstream issue is resolved.

amaitland avatar Jul 18 '25 08:07 amaitland

This appears to resolve the rendering issues, so we'll disable by default until the upstream issue is resolved.

GPU Compositing will be disabled by default in the WPF implementation until the upstream issue is resolved. Starting in M139.

amaitland avatar Aug 17 '25 01:08 amaitland