See the question and my original answer on StackOverflow

A site can define a Favicon (or not) using many ways, so here is a code that directly uses WebView2's FavIcon features: the FaviconChanged Event the FaviconUri Property and the GetFaviconAsync() Method.

So you can just use this:

tabViewItem.IconSource = new BitmapIconSource
{
    UriSource = new Uri(MyWebView.CoreWebView2.FaviconUri),
    ShowAsMonochrome = false,
};

I've been one step further in my context, as I wanted to use the favicon for the application icon too. This uses WinUI3's AppWindow.SetIcon method.

The tricky part is AppWindow.SetIcon only supports a file path to a .ico format file while WebView2 only support PNG or JPG streams. Luckily, .ico files can be encoded using PNG bytes (since Windows Vista).

Xaml:

<Grid>

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

    <StackPanel Orientation="Horizontal">
        <TextBox x:Name="url" />
        <Button x:Name="navigate" Click="navigate_Click">Go</Button>
    </StackPanel>

    <controls:WebView2
        x:Name="MyWebView"
        Grid.Row="1"
        HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch" />

</Grid>

C#:

In the following code, I avoid rewriting the .ico for a given favicon uri each time it changes but this is up to you.

public MainWindow()
{
    InitializeComponent();

    MyWebView.NavigationCompleted += MyWebView_NavigationCompleted;
}

private async void navigate_Click(object sender, RoutedEventArgs e)
{
    await MyWebView.EnsureCoreWebView2Async();
    MyWebView.CoreWebView2.Navigate(url.Text);
}

private void MyWebView_NavigationCompleted(WebView2 sender, CoreWebView2NavigationCompletedEventArgs args)
{
    // unhook from ourselves, we shouldn't need it anymore
    MyWebView.NavigationCompleted -= MyWebView_NavigationCompleted;
    
    // hook on favicon changes
    MyWebView.CoreWebView2.FaviconChanged += (s, e) => _ = UpdateFavicon();

    // first time set
    _ = UpdateFavicon();
}

private async ValueTask UpdateFavicon()
{
    var uri = MyWebView.CoreWebView2.FaviconUri;

    // in your TabViewItem case, this is where you would do
    // tabViewItem.IconSource = new BitmapIconSource...

    if (string.IsNullOrEmpty(uri))
    {
        // Note: when the site has no favicon (like with www.example.com),
        // uri is the empty string and GetFaviconAsync will return an 
        // "undefined" icon stream that can still be used to save a .ico.
        // Here I just remove the icon but we could continue
        // and use this "undefined" icon instead.
        AppWindow.SetIcon(null);
        return;
    }

    // come up with a unique (cache) file path
    using var md5 = MD5.Create();
    var name = new Guid(md5.ComputeHash(Encoding.UTF8.GetBytes(uri))) + ".ico";
    var path = Path.Combine(Path.GetTempPath(), name);

    // don't rewrite if already there
    if (!File.Exists(path))
    {
        // get PNG bytes
        using var iconStream = await MyWebView.CoreWebView2.GetFaviconAsync(CoreWebView2FaviconImageFormat.Png);

        // write to memory (shouldn't be too big)
        using var ms = new MemoryStream();
        using var stream = iconStream.AsStream();
        await stream.CopyToAsync(ms);
        stream.Position = 0;

        // decode it to determine width & height
        // seems WebView2 sends always 16x16 streams but not sure it's contractual
        var decoder = await BitmapDecoder.CreateAsync(ms.AsRandomAccessStream());

        // write icon in PNG format
        WritePNGIcon(path, decoder.PixelWidth, decoder.PixelHeight, ms.ToArray());
    }
    AppWindow.SetIcon(path);
}

public static void WritePNGIcon(string iconFilePath, uint width, uint height, byte[] pngBytes, int pngBytesIndex = 0, int pngBytesCount = int.MaxValue)
{
    ArgumentNullException.ThrowIfNull(iconFilePath);
    ArgumentNullException.ThrowIfNull(pngBytes);
    using var stream = File.OpenWrite(iconFilePath);
    WritePNGIcon(stream, width, height, pngBytes, pngBytesIndex, pngBytesCount);
}

public static void WritePNGIcon(Stream iconFileStream, uint width, uint height, byte[] pngBytes, int pngBytesIndex = 0, int pngBytesCount = int.MaxValue)
{
    ArgumentNullException.ThrowIfNull(iconFileStream);
    ArgumentNullException.ThrowIfNull(pngBytes);
    if (pngBytesCount == int.MaxValue || pngBytesCount < 0)
    {
        pngBytesCount = pngBytes.Length;
    }

    var startPos = iconFileStream.Position;
    var writer = new BinaryWriter(iconFileStream);
    writer.Write((short)0); // reserved
    writer.Write((short)1); // image type (1 for .ico)
    writer.Write((short)1); // count
    const int maxIconSize = 256;
    writer.Write(width == maxIconSize ? (byte)0 : (byte)width);
    writer.Write(height == maxIconSize ? (byte)0 : (byte)height);
    writer.Write((byte)0); // number of colors in the color palette
    writer.Write((byte)0); // reserved
    writer.Write((short)1); // color planes
    writer.Write((short)32); // bits per pixel (ARGB=32)

    var offsets = iconFileStream.Position;
    writer.Write(0); // size of image data, we'll rewrite that later
    writer.Write(0); // offset of data from beginning;

    var pngOffset = iconFileStream.Position - startPos;
    writer.Write(pngBytes, pngBytesIndex, pngBytesCount);

    // save end position
    var endPos = iconFileStream.Position;
    iconFileStream.Seek(offsets, SeekOrigin.Begin);
    writer.Write(pngBytesCount);
    writer.Write((int)pngOffset);
    iconFileStream.Position = endPos; // restore end position
}

Results on www.stackoverflow.com:

enter image description here

Note: the .ico path can also be used as the UriSource for the TabViewItem.