MicrosoftEdge / WebView2Feedback

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

WebView2.Source property throws NotImplementedException when passed a null value #1330

Open bozalina opened 3 years ago

bozalina commented 3 years ago

This seems like the incorrect exception type for this error. Perhaps an ArgumentNullException or InvalidOperationException would be more appropriate.

private static void SourcePropertyChanged( DependencyObject d, DependencyPropertyChangedEventArgs e) { Microsoft.Web.WebView2.Wpf.WebView2 control = (Microsoft.Web.WebView2.Wpf.WebView2) d; if (control.IsPropertyChangingFromCore(Microsoft.Web.WebView2.Wpf.WebView2.SourceProperty)) return; Uri oldValue = (Uri) e.OldValue; Uri newValue = (Uri) e.NewValue; if (newValue == (Uri) null) throw new NotImplementedException("The Source property cannot be set to null."); if (!newValue.IsAbsoluteUri) throw new ArgumentException("The Source property cannot be set to a relative Uri."); if (control.CoreWebView2 != null && (oldValue == (Uri) null || oldValue.AbsoluteUri != newValue.AbsoluteUri)) control.CoreWebView2.Navigate(newValue.AbsoluteUri); control.WhenInit_Source((Action) (() => control.EnsureCoreWebView2Async())); }

AB#33616111

champnic commented 3 years ago

Thanks for the catch here - I think you are right that those proposed exception types may be more appropriate. My only concern is if someone is already specifically catching the "NotImplementedException" and then would miss this, but I imagine that scenario is rare. I've opened a bug on our backlog. Thanks!

PatrickHofman commented 3 years ago

And based on issue #1422, I would suggest to allow null values, as MVVM binding decoupling might lead to null values being pushed to the browser control.

I would suggest to show the 'about:blank' page, or something similar in case of a null value.

PatrickHofman commented 2 years ago

A year later, this issue still causes us headaches. Is there any progress on this issue?

jpgarza93 commented 2 years ago

Having same issue

darbid commented 2 years ago

yes one of the main headaches is when using a WPF tabcontrol where unused tabs are automatically "removed"

oldsand commented 2 years ago

I am facing the same issue............

josephdrake-stahls commented 1 year ago

Not sure if this will be useful to anyone, but this is my workaround. There are 2 attached dependency properties here.

The use case for this was with the Telerik RadTabbedWindow with IsContentPreserved set to true. Dispose needs to be called when the control is outright removed because WebView2 will live in the background (a new thread for every tab).

Mileage may vary since (I think) the Loaded/Unloaded event are tied to the control being added/removed from the visual tree. But this solution might give someone a better idea.

using Microsoft.Web.WebView2.Wpf;
using System;
using System.Windows;
using System.Windows.Controls;

namespace WebView2Test;

public static class WebView2Helper
{
    #region NullableSource
    [AttachedPropertyBrowsableForType(typeof(WebView2))]
    public static Uri? GetNullableSource(DependencyObject obj) => (Uri?)obj.GetValue(NullableSourceProperty);
    public static void SetNullableSource(DependencyObject obj, Uri? value) => obj.SetValue(NullableSourceProperty, value);

    /// <summary>
    /// Middle man for binding <see cref="WebView2.Source"/> when the binding might resolve to null.
    /// </summary>
    /// <remarks>
    /// <see cref="WebView2.Source"/> can become null when <see cref="WebView2"/> is used in a <see cref="ItemsControl.ItemTemplate"/>. When an item is removed from the <see cref="ItemsControl.ItemsSource"/>, the Binding to <see cref="WebView2.Source"/> will resolve to null prior to the control being unloaded.
    /// </remarks>
    public static readonly DependencyProperty NullableSourceProperty = DependencyProperty.RegisterAttached("NullableSource", typeof(Uri), typeof(WebView2Helper), new PropertyMetadata(propertyChangedCallback: NullableSourceChanged));
    private static void NullableSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not WebView2 webView2)
            return;

        webView2.Source = e.NewValue as Uri ?? new Uri("about:blank");
    }
    #endregion

    #region DisposeOnUnloaded
    [AttachedPropertyBrowsableForType(typeof(WebView2))]
    public static bool GetDisposeOnUnloaded(DependencyObject obj) => (bool)obj.GetValue(DisposeOnUnloadedProperty);
    public static void SetDisposeOnUnloaded(DependencyObject obj, bool value) => obj.SetValue(DisposeOnUnloadedProperty, value);

    /// <summary>
    /// When true, disposes <see cref="WebView2"/> when <see cref="FrameworkElement.Unloaded"/> is fired.
    /// </summary>
    /// <remarks>
    /// By default, <see cref="WebView2"/> does not dispose of its resources when its unloaded, which might be desirable in certain situations.
    /// </remarks>
    public static readonly DependencyProperty DisposeOnUnloadedProperty = DependencyProperty.RegisterAttached("DisposeOnUnloaded", typeof(bool), typeof(WebView2Helper), new PropertyMetadata(false, propertyChangedCallback: DisposeOnUnloadedChanged));

    private static void DisposeOnUnloadedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is not WebView2 webView2)
            return;

        //always attempt to unsubscribe in case this get triggered for "true" multiple times
        webView2.Unloaded -= WebView2_Unloaded;
        if (e.NewValue is true)
            webView2.Unloaded += WebView2_Unloaded;
    }

    private static void WebView2_Unloaded(object sender, RoutedEventArgs e)
    {
        if (sender is WebView2 webView2)
        {
            webView2.Dispose();
            webView2.Unloaded -= WebView2_Unloaded;
        }
    }
    #endregion
}
<wpf:WebView2 
    local:WebView2Helper.NullableSource="{Binding SomeUri}"
    local:WebView2Helper.DisposeOnUnloaded="True"/>
ekalchev commented 1 week ago

I can propose better workaround of this issue for those that already subclassing WebView2

    public partial class WebView : WebView2
    {
        static WebView()
        {
            SourceProperty.OverrideMetadata(typeof(WebView), new PropertyMetadata(defaultValue: new Uri("about:blank")));

            DefaultStyleKeyProperty.OverrideMetadata(typeof(WebView), new FrameworkPropertyMetadata(typeof(WebView)));
        }