MicrosoftEdge / WebView2Feedback

Feedback and discussions about Microsoft Edge WebView2

Home Page:https://aka.ms/webview2

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Flash when using Multiple WebViews in Tab Controls

RickStrahl opened this issue · comments

I'm running into major flashing issues with the control when using WebView2 in a tab control where there are multiple tab controls activated for each tab.

Here's a screen capture that demonstrates:

TabFlicker

(the screen capture enhances this behavior, it's not quite as noticeable as this capture shows it but definitely noticeable)

Notice the flicker on every tab switch. In this case, the WebView2 here holds a rendered HTML document that's already loaded and rendered and is not reloaded when a new tab is activated and the page is not being reloaded when the tab changes in this case - it's merely being displayed again.

In this case the WV is already rendered so there no other content loading that is empty first.

A few additional points:

  • If I hide the tabs the flicker disappears on tab switches (ie. it's not in the tabs themselves)
  • I've set the background color for the WV, the content area below etc. which works for activation but not deactivation apparently

I'm guessing what's happening is that when the tab is deactivated the WV gets removed from the container and that's when the unformatted white 'hole' appears briefly before the new page gets activated and overlaid. If I had to guess this is the same issue we were dealing with the start up flash a while back except now when the control is being moved out of the container.

FWIW I also had this to a much less noticeable extent with the WebBrowser control and likely due to the same semantics of removing the windowed control and reinserting a different one.

I haven't figure out a way around this. During startup I can hide the control and make it visible only after content has loaded, which I have working - albeit with a noticeable and annoying startup delay. However, I can't do the same for the tab deactivation re-activation as there is no clean hook I can find to make the control invisible before the tab switch occurs.

Original Bug: AB#33815184
DOMContentLoaded Bug: AB#34093671
Visibility Bug: AB#34093759

Do you have control over how the controls are shown and hidden, or is that all controlled by a tabbing control? Could you "overlap" them - ie. when switching tabs show the new tab, and then hide the old tab as opposed to hide old tab, then show new tab?

We are currently looking into a white flicker bug that may be related - @johna-ms FYI in case this is the same issue.

@RickStrahl That's what I currently do, which is an improvement.

@champnic I've been trying to intercept the tab change, before it switches to the new view, but haven't found a way to do this. All the events/hooks I've tried fire too late and either hide the new tab, or have no effect (ie. too late hiding) and I get the flash anyway.

@ukandrewc What are you doing exactly? I haven't found a way to hide the control based on tab changes. I suppose I could use mouse click events, but I have lots of scenarios that are programmatically or binding related updates that won't trigger then.

Should try the mouse behavior though to at least see if that would even work to hide the flash. I think part of the problem is that the way the operation is dispatched, hiding happens after the flicker.

@RickStrahl I'm creating my own tabs, so can show before hide, but based on your new post, I see you can't do that.

	'Show before hide to prevent flashing
	If Tab IsNot Nothing Then
		Tab.Web.BringToFront()
		Tab.Web.Show()
	End If

	'Hide and deactivate previous tab
	If _ActiveTab IsNot Nothing Then
		_ActiveTab.Active = False
		_ActiveTab.Web.Hide()
	End If

@RickStrahl I've added this bug to our backlog.

If I hide the tabs the flicker disappears on tab switches (ie. it's not in the tabs themselves)

When you say you "hide the tabs" do you mean you hide the WebView2 in the tabs? If the make the tabs a different background color (hot pink) the flash remains white?

So I've made a few more attempts at this but without any luck.

Specifically, I've added code to explicitly hide the control in the Tab's PreviewMouseLeftButton event, and then make it visible again in the TabSelection event. What I'm finding is that has no effect and I get the same flashing behavior. Even delaying the visibility display doesn't help which seems to suggest the flash occurs during hiding.

Next I also tried to make the control invisible and not make it visible again explicitly - IOW, I'm just hiding it - in most situations I also see the flash then.

If I keep the control hidden then there's no flash so that rules out that there's some problem with the Tab control or some other sizing or overlay issue.

Another note:

I have the DefaultBackgroundColor set to match the form's background. When I just dump the browser control onto form and use a button to toggle visibility I also see the occasional flash - it's not anywhere as consistent as with the tab control, but it still happens there as well.

When it flashes I'm noticing that the flash will be white briefly before briefly flashing the background color (I set it green so I can see it). IOW, it looks like the background is being applied but there still is a problem with a brief white flash on activation (or deactivation).

@RickStrahl Thanks for confirming those extra details. Are you using DComp visual hosting mode, or just the regular HWND hosting (which is the default and used by the WPF and WinForms controls)?

@RickStrahl I've just tried a couple of sample apps with black background behind the WebView2, WebView2.DefaultBackgroundColor = black, and loading a black image (all to make flashes glaringly obvious). Then I have a button that toggles the visibility of the WebView2. I haven't been able to see any flashes just by toggling the visibility of WebView2 in either WinForms or WPF. Any thoughts on what your code might be doing differently that would help to reproduce this issue? Or do you have a sample app that repros the issue we could look at?

I'm using regular control hosting in WPF. In my case the control is hosted in UserControl which is then hosted inside of a tab page. User control has two WV controls on it.

So this a pretty complex setup, which is why I've taken all these extra steps to make sure there's not something else going on with the flashing. And I used the same setup with Web Browser which didn't flash like this.

If I get some time I'll set up a sample form to check, or maybe you can share your's as a base line?

If necessary I can give access to the repo but it's 'semi-private' due to blatant rebranding issues I've had in the past :-( No problem sharing but not sure how useful because of the activation complexity.

@champnic, so I have some more follow up on this topic.

After a lot of trial and error I've managed to work around the screwy WebView2 flashing behavior.

Here are a few things I found out along the way:

  • Flashing during start up
  • Flashing with tab Switches
  • Flashing when tab is closing

Flashing caused by Interop During Load

For startup I explicitly set the visibilty of the control to hidden during startup, and then make it visible only when the control first loads content. I also set the DefaultBackgroundColor to match the host form background at the same time.

However, despite doing these things I get a significant white flash when my content is loading.

What I found was that this doesn't happen if I have simple content that just loads on its own. IOW, if I load a static page that just runs in the browser there's no flashing (other than startup background flash which is minor). In my app however I intercept CoreWebView2_DOMContentLoaded to know when the doc has loaded.

If in that code I immediately start firing Interop commands I get a white flash in the WebView2 control.

private void CoreWebView2_DOMContentLoaded(object sender, CoreWebView2DOMContentLoadedEventArgs e)
{
    if (WebBrowser.Visibility != Visibility.Visible &&
        WebBrowser.Source == null ||
        !WebBrowser.Source.ToString().Contains("/editor.htm"))
        return;


    // HACK: Very hacky code related to making the control not flicker
    // on first render handle visibility here so we show 'late'
    // after control has completed loading content
    //
    // Otherwise: visibility is hide/set in Tab_SelectionChanged
    // WebBrowser is hidden in Model.ActiveEditorTabItem_Set()
    if (IsFirstRender)
    {
        IsFirstRender = false;
        ShowWebBrowser();
    }

   // without this there's almost always flicker. With this no flicker
    WebBrowser.Dispatcher.Invoke(async () =>
    {
        JsInterop = new EditorWebViewJavaScriptInterop(WebBrowser, DotnetInterop);
        
        // Call JavaScript method to initialize the document
        // this essentially launches the editor, sets styles etc.
        await JsInterop.InitializeInterop();

        if (Editor.MarkdownDocument.EditorSyntax != "markdown")
            await JsInterop.SetLanguage(Editor.MarkdownDocument.EditorSyntax);

        if (Editor.MarkdownDocument.EditorSyntax == "markdown" ||
            Editor.MarkdownDocument.EditorSyntax == "text")
            await JsInterop.EnableSpellChecking(!mmApp.Configuration.Editor.EnableSpellcheck,
                mmApp.Configuration.Editor.Dictionary);
        else
            // always disable for non-markdown text
            await JsInterop.EnableSpellChecking(false, mmApp.Configuration.Editor.Dictionary);

        await Editor.SetMarkdown();

        if (!Editor.NoInitialFocus)
            await Editor.SetEditorFocus();

        if (Editor.InitialLineNumber > 0)
        {
            _ = Window.Dispatcher.InvokeAsync(
                async () => { await Editor.GotoLine(Editor.InitialLineNumber); },
                DispatcherPriority.Background);
        }

        if (Editor.TabLoadingCompleted != null)
            await Editor.TabLoadingCompleted.Invoke(Editor);

    }, DispatcherPriority.Background);
}

If I run the code above without the Dispatcher block (with the method signature async void) there's a big white flash during activation. Using the dispatcher code as shown seems to mitigate this and give the page enough time to complete its render cycle before the interop calls into the page are fired and which avoids the flicker.

To me this looks like starting interop calls before the page has completed rendering here is the problem and by using the dispatcher it delays the potential UI thread locking long enough for the page to cleanly display.

I consider this a bug. It seems to me your WPF wrapper code needs to ensure that the control has actually rendered the DOM properly and can display whatever the original content is to be displayed. IOW, it seems like the Dispatcher call I'm making should occur before the event is fired to ensure that the UI is rendered cleanly.

Tab Activation - Visibility

I was also am seeing major flashing issues every time I would:

  • Switch tabs
  • Close a tab

In my scenario I'm swapping one tab page that contains a WebView2 with another that also contains a WebView2 (already rendered). In theory there should be no problem as the tab page is made invisible and the WebView should be made invisible with it as the new one is activated.

Instead I get a major white flash.

To Fix this I had to do the following;

  • Intercept the Tab Change
  • Hide the original WebView2
  • Wait on the UI (fake DoEvents())
  • Make the new WebView2 visible

This is a hot mess to say the least especially the second bullet point.

Let me walk you through this crap:

To capture the Tab change I have a binding for SelectedItem and in that getter I can intercept the Tab change:

public TabItem ActiveEditorTabItem
{
    get { return _activeEditorTabItem; }
    set
    {
        if (value == _activeEditorTabItem) return;

        var oldEditor = _activeEditorTabItem?.Tag as MarkdownDocumentEditor;

        _activeEditorTabItem = value;
        OnPropertyChanged(nameof(ActiveEditorTabItem));
        SelectedEditor = value?.Tag as MarkdownDocumentEditor;

        //// hide the old WebBrowser Control
        if (oldEditor?.EditorHandler.WebBrowser != null)
        {
            oldEditor.EditorHandler.WebBrowser.Visibility = Visibility.Hidden;
            
            // REQUIRED: DoEvents like. Without this this hack doesn't work
            oldEditor.EditorHandler.WebBrowser.Dispatcher.Invoke(() => { }, 	DispatcherPriority.Background);
        }
    }
}

The idea is to detect the tab change (which is fine), hide the original tab (before the TabSelection occurs) and this works to do this.

Notice however, that setting the visibility on its own is not enough! You have to also let the UI catch up with the equivalent of a DoEvents() (via an empty dispatcher call). Without the Dispatcher call the visibility setting has no effect.

I think the WebView2 should do this internally and force the visibility change through when it is applied or at least handle the sequence correctly. Without the Dispatcher invoke, the visibiltiy check is basically ignored or at minimum too late to have an effect.

I suggest that the control should apply the visibility change immediately - if that was the case I think all of the issues I describe here would be alleviated.

Closing Tabs

I also get the same flashing issue when closing tabs which seems to suggest that the problem with the flashing is related to hiding rather than showing the control.

Again I tried just explicitly setting the visibility to hidden, but that didn't work, and instead I need to use the same DoEvents like trick.

This code works:

public void HideWebBrowser()
{
    if (WebBrowser != null && WebBrowser.Visibility != Visibility.Hidden)
    {
        WindowUtilities.DoEvents();
        WebBrowser.Visibility = Visibility.Hidden;
        WindowUtilities.DoEvents();
    }
}

If I remove the DoEvents() - either one of them - I'm back to the white flash.

With that code above it works, but - that simply should not be necessary and has slowing implications in a WPF application which doesn't have 'real' DoEvents() functionality.

Wrapup

Prior to the fixes the flashing was not consistent. I can open the same document multiple times, sometimes it'll flash others not. Flip between tabs, sometimes I see the flash, sometimes not. When it does happen though - it's not respecting the DefaultBackgroundColor - the flash is white. I managed to get the debugger to stop on a few of those white flashes (prior to DoEvents calls usually) and I can see pure white rectangle that fills the WebBrowser control area.

I sort of understand why white flashes occur during startup, due to the Interop. But in the tab switching and even more so the closing scenario it makes no sense that contorl is flashing. It appears that setting the visibility to hidden is what the problem.

In summary here are a few suggestions:

  • CoreWebView2_DOMContentLoaded should ensure the control itself has fully rendered (maybe not content, but the control itself and the canvas). If that's not possible maybe there needs to be another event that fires and gives you a heads up when content is ready to display. Simple solution here seems: Dispatch() the event before it's fired.

  • Visibility handling should fire immediately or if that can't be done should ensure the UI catches up to it. ie. internally the equivalent of a DoEvents that ensures the changes are immediately applied.

The latter especially seems important because there's so little control over the visibility handling otherwise.

Thanks Rick for a really fantastic investigation and writeup! I've opened bugs to track your suggestions. We're still looking at the underlying source of the white, which should instead be using the default background color always, and I believe would alleviate these issues. We're having trouble tracking it down, so I'm glad you've got workarounds in the meantime (though I'm sorry that figuring them out was probably a lot of work). Thanks!

Unfortunately the workarounds are not very good - they result in delayed loading behavior and in some cases get 'stuck' until the UI is activated because they are basically introducing wait states.

The key problem seems to be deactivation of the control when hiding it and then re-displaying it.

If I just do a load and ste the default background to green I can see the green background flash briefly. However for the tab switch that color never shows and there's what appears to be a double flash - presumably as it's hidden on the deactivating tab, and then re-seated in the new tab.

So I think the focus of what's broken is not initial loading but when the control gets removed/hidden from its container and then reactivated non-interactively.

Again I think this can be solved with properly dealing with visibility immediately rather than going through the event queue.

@chapnic - On another side note just to make this even more odd.

Additionally - the control does not like to be loaded when it's not visible. It seems the UI behavior and speed changes drastically when the control is not visible vs visible. If I ignore all the visibility trickery I'm doing and just let the control run as it naturally would, performance seems 'normal' - things just snap into place (normal except the massive flashing that then occurs) . But when the controls are hidden, there is delay and jankiness in the controls rendering.

To be clear I make the control 'hidden' and make it visible only in the first 'ContentLoaded' handler.

Another maybe more annectdotal problem is that I have two controls that in the same user ocntrol that are rendering simultaneously on startup. I have an JS editor and a preview and the preview is fired after the editor is loaded. The preview always renders very quickly (because it's basically static HTML) while the editor is the one that flickers and has issues (presumably due to the interop calls and inital load overhead). But... if I hide the preview and load the same documents, the performance is drastically better. The odd and inconsistent 'hang' delays go away and the editor then loads consistently. If the Preview is turned back on I get varying load time behaviors.

FlickerDualWebViews

Note this is with the 'flicker mitigations' in place.

The first few loads are using both the editor and preview and you can see that the editor load times vary considerably. The first load (which isn't the very first render - I opened a few times before the capture) is very slow, while the second and third loads a are quicker. If I do this 10 times I get 10 different lengths of time for those loads.

The last few loads use only the editor and you can see the load times are fairly consistent. The code runs the exact same load logic in both cases the only difference is that it's not also loading the preview at the same time in parallel. I would expect some overhead in simutaneous rendering, but the delay when it happens can be quite drastic (as in the first load where it hangs well over a second before 'catching').

This seems to suggest that the control are interfering with each other in terms of timnig. The Preview always renders quick and just snaps into place (it also hides -> shows in Content Loaded) but the editor has the varying behavior.

@champnic Just to give you an idea how bad of a problem the flicker is all around in this. Here's a short screen cap of this with all the mitigations disabled and only using default background and visibilty until first content load set.

FlickerDualWebViews2

Notice that the flicker is not consistent either. Sometimes there is the white flash and other times there's not. Default background is set on both controls to match the background of the window and visibilty is enabled only after the first content render. I removed the mitigation for tab switches which hides the control, the restores it on the new tab.

None of this should be necessary if the control properly respected its visibility settings.

Notice also the dual flash on the first load of a new tab which I think is from:

  • Original tab disabling (flash1)
  • New tab loading new document and browser control

It's unusable in that state frankly. With my mitigations it's better but there are problems. I really would expect this unmitigated case to work without flashing.

I can only speak about WPF. I had the same problem. My workaround is to WebView2's initial visibility to Hidden. Load the URI or content. Subscribe to NavigationCompleted event and there i set WebView2's visibility to Visible. This works in all cases and is relatively easy.

The white flash is because WebView2 is currently running in Edge's light mode. If it has no content loaded the background is white, its called blank page. It won't load html content if its not part of the visual tree. So it gets added to the visual tree with default blank white page and then starts to load the HTML content. The problem may be solved once developers get the ability to enable WebView2 (Edge) dark mode. Hopefully this can be done before WebView2 is visible / part of the visual tree so the first initial blank page is dark. Maybe WebView2/Edge team can tell if this will be the case.

Another solution is that the Webview2's blank page gets the background color of the WebView2 WPF control. I expect this to be difficult though, the WPF control seems to be just a host control for a native control.

commented

@champnic I've been trying to intercept the tab change, before it switches to the new view, but haven't found a way to do this. All the events/hooks I've tried fire too late and either hide the new tab, or have no effect (ie. too late hiding) and I get the flash anyway.

@ukandrewc What are you doing exactly? I haven't found a way to hide the control based on tab changes. I suppose I could use mouse click events, but I have lots of scenarios that are programmatically or binding related updates that won't trigger then.

Should try the mouse behavior though to at least see if that would even work to hide the flash. I think part of the problem is that the way the operation is dispatched, hiding happens after the flicker.

Hi, @RickStrahl. You can intercept the tab change by override metadata of SelectedItemProperty and do it in the coerce fuction. I have some idea about the flashing of WebView2 here.

NewBehavior2

Hi all. We are working on exposing a DefaultBackgroundColor option on the controller that will likely fix this. @liwuqingxin could you try setting this environment variable before running your app WEBVIEW2_DEFAULT_BACKGROUND_COLOR 0x00000000

But replace 0x00000000 with the hex value of your desired background color. The bytes are read in ARGB order. If you like you can also test with 0 since that defaults the WebView to be transparent background. However this makes performance a little worse.

commented

Hi all. We are working on exposing a DefaultBackgroundColor option on the controller that will likely fix this. @liwuqingxin could you try setting this environment variable before running your app WEBVIEW2_DEFAULT_BACKGROUND_COLOR 0x00000000

But replace 0x00000000 with the hex value of your desired background color. The bytes are read in ARGB order. If you like you can also test with 0 since that defaults the WebView to be transparent background. However this makes performance a little worse.

Hi, @johna-ms . Thank you for your advice. :) I don't figure out the difference between environment variable and CoreWebView2.DefaultBackgroundColor. And I think that will not fix this problem because there is a very short period during which WebView2 control disappers because of loading and unloading mechanism of TabControl in WPF.

In the image below, "Flashing#1" stage is the period. DefaultBackgroundColor of WebView2 does not help for WebView2 disappering. It will help in "Flashing#2" stage. We can see the RED color of the WebView2 in this stage and we can change the color to any value we prefer. "Target Page Loading" stage is out of our quesion.

In conlusion the main and basic limits are the loading performance of WPF and the render speed of webView2. The best solution is to improve performance of those, if it is possiable 😄 . If not, I have some tricks to avoid the flashing here.

image

@liwuqingxin quick question about your blog post, where is your little loading animation located? Is it simply behind the webview2?

commented

@liwuqingxin quick question about your blog post, where is your little loading animation located? Is it simply behind the webview2?

@darbid It's not. The loading control contains the WebView2.

    public class WebView2Wrapper : Loading
    {
        public readonly WebView2 WebView2;

        private readonly ContentControl _contentControl;

        public Uri Source
        {
            get => WebView2.Source;
            set => WebView2.Source = value;
        }

        public WebView2Wrapper()
        {
            WebView2 = new WebView2();
            WebView2.NavigationCompleted += OnNavigationCompleted;

            _contentControl = new ContentControl
            {
                Content = WebView2,
                RenderTransform = new TranslateTransform(-10000, -10000)
            };

            Content = _contentControl;
            IsOpen  = true; // Property IsOpen controls the loading.
        }

        private void OnNavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
        {
            _contentControl.RenderTransform = new TranslateTransform(0, 0);
            IsOpen = false; // Stop the loading animation.
        }
    }

Closing this. With the provided environment variable workaround, remaining flashes appear to be from WPF framework rendering WebView2 or webpage css background. Please reopen if this is still an issue

@johna-ms I'd like to request reopening this if possible. I have experimented embedding WebView2 into Win32, Qt, and WPF apps and from what I can tell, painting the background color just prior to a transition makes for a bad workaround. The WebView2 control flicker/flash prior to the first meaningful paint is noticeably bad relative to other solutions and native controls, so I am abandoning WV2 support for the time being (will be subscribing to notifications from this thread if there are any future changes though).