Skip to content

Commit 264fb80

Browse files
authored
Merge pull request #69 from AvaloniaUI/fixes/auth-dialog-background
NativeWebDialog.DefaultBackground integration
2 parents 2105ee4 + 8afba7e commit 264fb80

9 files changed

Lines changed: 164 additions & 14 deletions

File tree

src/Avalonia.Controls.WebView.Core/Android/AndroidNativeWebViewDialog.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal class AndroidNativeWebViewDialog(Action<WebViewEnvironmentRequestedEven
2121

2222
public IWebViewAdapter? TryGetAdapter() => AndroidWebViewDialogActivity.WebViewRegistry.Get(_webViewId);
2323

24-
public Color DefaultBackground { get; set; }
24+
public Color DefaultBackground { get; set; } = Colors.White;
2525
public string? Title { get; set; }
2626

2727
public bool CanUserResize { get => false; set { } }

src/Avalonia.Controls.WebView.Core/Browser/BrowserWindowNativeWebViewDialog.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ internal class BrowserWindowNativeWebViewDialog(Action<WebViewEnvironmentRequest
2121
private IWebViewAdapter? _adapter;
2222
private Action? _unsubClose;
2323
private string? _title;
24-
private Color _defaultBackground;
24+
private Color _defaultBackground = Colors.White;
2525
private bool _disposed;
2626

2727
public IWebViewAdapter? TryGetAdapter() => _adapter;

src/Avalonia.Controls.WebView.Core/Gtk/GtkInterop.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ internal static partial IntPtr webkit_website_data_manager_new(
142142
public static extern nint webkit_web_view_run_javascript_finish(IntPtr webView, IntPtr result, GError** error);
143143

144144
[DllImport(LibWebKit)]
145-
public static extern void webkit_web_view_set_background_color(IntPtr webView, GdkRGBA color);
145+
public static extern void webkit_web_view_set_background_color(IntPtr webView, GdkRGBA* color);
146146

147147
[DllImport(LibWebKit)]
148148
public static extern void webkit_javascript_result_unref(IntPtr jsResult);

src/Avalonia.Controls.WebView.Core/Gtk/GtkNativeWebViewDialog.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,33 @@ internal sealed class GtkNativeWebViewDialog : INativeWebViewDialog, IGtkWebView
2525
private bool _isShown;
2626

2727
private sealed class DialogGtkWebViewAdapter(GtkWebViewEnvironmentRequestedEventArgs args)
28-
: GtkWebViewAdapter(args);
28+
: GtkWebViewAdapter(args)
29+
{
30+
public override Color DefaultBackground
31+
{
32+
set
33+
{
34+
var rgba = new GdkRGBA
35+
{
36+
red = value.R / 255.0,
37+
green = value.G / 255.0,
38+
blue = value.B / 255.0,
39+
alpha = value.A / 255.0
40+
};
41+
RunOnGlibThreadAsync(() =>
42+
{
43+
if (WebViewHandle == IntPtr.Zero)
44+
return;
45+
46+
unsafe
47+
{
48+
var color = rgba;
49+
webkit_web_view_set_background_color(WebViewHandle, &color);
50+
}
51+
});
52+
}
53+
}
54+
}
2955

3056
private GtkNativeWebViewDialog(GtkWebViewEnvironmentRequestedEventArgs args)
3157
{

src/Avalonia.Controls.WebView.Core/Headless/HeadlessWebViewAdapter.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,14 @@ public Uri Source
6666

6767
public Color DefaultBackground
6868
{
69-
set
70-
{
71-
}
69+
set => LastDefaultBackground = value;
7270
}
7371

72+
/// <summary>
73+
/// Last value assigned to <see cref="DefaultBackground"/>, useful with unit testing.
74+
/// </summary>
75+
internal Color? LastDefaultBackground { get; private set; }
76+
7477
public string? UserAgent { get => null; set { } }
7578

7679
public void SizeChanged(PixelSize containerSize)

src/Avalonia.Controls.WebView/NativeWebDialog.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using AvPlatform = Avalonia.Platform;
88
using Core = Avalonia.Controls;
99
using IPlatformHandle = Avalonia.Platform.IPlatformHandle;
10+
using Color = Avalonia.Media.Color;
11+
using Colors = Avalonia.Media.Colors;
1012
#if WPF
1113
using AvaloniaUI.Xpf.WpfAbstractions;
1214
using Window = System.Windows.Window;
@@ -38,6 +40,7 @@ public class NativeWebDialog : Core.IWebView, Core.IWebViewHolder, IDisposable
3840
private bool? _initialCanUserResize;
3941
private PixelSize? _initialSize;
4042
private PixelPoint? _initialPosition;
43+
private Color? _initialDefaultBackground;
4144
private bool _disposed;
4245
private bool _dialogInitialized;
4346

@@ -276,6 +279,24 @@ public bool CanUserResize
276279
}
277280
}
278281

282+
/// <summary>
283+
/// Gets or sets the background color of the dialog and of the webview hosted inside of it.
284+
/// If null, the owner background is used, falling back to white.
285+
/// </summary>
286+
public Color? DefaultBackground
287+
{
288+
get => _initialDefaultBackground;
289+
set
290+
{
291+
_initialDefaultBackground = value;
292+
if (value is { } background
293+
&& TryGetImpl() is { } impl)
294+
{
295+
impl.DefaultBackground = background;
296+
}
297+
}
298+
}
299+
279300
/// <inheritdoc cref="Core.INativeWebViewDialog.Closing"/>
280301
public event EventHandler? Closing;
281302
/// <inheritdoc cref="Core.INativeWebViewDialog.Show()"/>
@@ -295,6 +316,12 @@ public async void Show(TopLevel owner)
295316
{
296317
var impl = await GetOrInitialize();
297318

319+
// Not stored in _initialDefaultBackground, so the owner is still resolved again on the next Show call.
320+
if (_initialDefaultBackground is null)
321+
{
322+
impl.DefaultBackground = GetOwnerBackground(owner);
323+
}
324+
298325
#if WPF
299326
var avTopLevel = XpfWpfAbstraction.GetAvaloniaTopLevelForWindow(owner);
300327
#elif AVALONIA
@@ -317,6 +344,18 @@ public async void Show(TopLevel owner)
317344
}
318345
}
319346

347+
#if WPF
348+
private static Color GetOwnerBackground(Window owner) =>
349+
owner.Background is System.Windows.Media.SolidColorBrush solid ?
350+
new Color(solid.Color.A, solid.Color.R, solid.Color.G, solid.Color.B) :
351+
Colors.White;
352+
#elif AVALONIA
353+
private static Color GetOwnerBackground(TopLevel owner) =>
354+
owner.Background is Media.ISolidColorBrush solid ?
355+
solid.Color :
356+
Colors.White;
357+
#endif
358+
320359
private async Task<Core.INativeWebViewDialog> GetOrInitialize()
321360
{
322361
if (TryGetImpl() is { } impl)
@@ -439,6 +478,8 @@ private async Task Initialize()
439478
dialogImpl.Move(position.X, position.Y);
440479
if (_initialSize is { } size)
441480
dialogImpl.Resize(size.Width, size.Height);
481+
if (_initialDefaultBackground is { } background)
482+
dialogImpl.DefaultBackground = background;
442483

443484
_implTcs.SetResult(dialogImpl);
444485

src/Avalonia.Controls.WebView/WebAuthenticationBroker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ private static NativeWebDialog DefaultFactory()
146146
{
147147
var dialog = new NativeWebDialog();
148148
dialog.Title = "Authentication";
149-
dialog.CanUserResize = false;
149+
dialog.CanUserResize = true;
150150
dialog.Resize(600, 700);
151151
return dialog;
152152
}

src/Avalonia.Controls.WebView/WindowNativeWebViewDialog.cs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,10 @@ async void CompleteAdapter()
5656

5757
_controlHostImpl.AdapterCreated += (_, adapter) =>
5858
{
59-
adapter.DefaultBackground = _initialDefaultBackground ?? Colors.Transparent;
59+
// Opaque by default, so web pages without their own background stay readable with a dark theme.
60+
var background = _initialDefaultBackground ?? Colors.White;
61+
adapter.DefaultBackground = background;
62+
ApplyWindowBackground(background);
6063
AdapterCreated?.Invoke(this, new Core.WebViewAdapterEventArgs(adapter));
6164
};
6265
_controlHostImpl.AdapterDestroyed += (_, adapter) => AdapterDestroyed?.Invoke(this, new Core.WebViewAdapterEventArgs(adapter));
@@ -77,17 +80,26 @@ public Color DefaultBackground
7780
{
7881
set
7982
{
83+
_initialDefaultBackground = value;
8084
if (_controlHostImpl?.TryGetAdapter() is { } adapter)
8185
{
8286
adapter.DefaultBackground = value;
8387
}
84-
else
85-
{
86-
_initialDefaultBackground = value;
87-
}
88+
ApplyWindowBackground(value);
8889
}
8990
}
9091

92+
// The webview may be composited into the window (or transparent on macOS),
93+
// so the window itself has to use the same color.
94+
private void ApplyWindowBackground(Color color)
95+
{
96+
#if WPF
97+
Background = new SolidColorBrush(System.Windows.Media.Color.FromArgb(color.A, color.R, color.G, color.B));
98+
#elif AVALONIA
99+
Background = new Media.SolidColorBrush(color);
100+
#endif
101+
}
102+
91103
public void Dispose() => Close();
92104

93105
event EventHandler? Core.INativeWebViewDialog.Closing

tests/Avalonia.Controls.WebView.Tests/NativeWebDialogTests.cs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
using Avalonia.Headless.XUnit;
1+
using Avalonia.Controls.Headless;
2+
using Avalonia.Headless.XUnit;
3+
using Avalonia.Media;
24
using System;
35
using System.Threading.Tasks;
46
using Xunit;
@@ -56,6 +58,72 @@ public void Should_Set_And_Reflect_Position_And_Size()
5658
Assert.Equal(60, underlying.Position.Y);
5759
}
5860

61+
[AvaloniaFact]
62+
public async Task Should_Use_White_Background_Without_Owner()
63+
{
64+
var dialog = new NativeWebDialog();
65+
dialog.Show();
66+
67+
await WaitForAdapterCreation(dialog);
68+
69+
Assert.Null(dialog.DefaultBackground);
70+
Assert.Equal(Colors.White, GetAdapterBackground(dialog));
71+
Assert.Equal(Colors.White, GetWindowBackground(dialog));
72+
}
73+
74+
[AvaloniaFact]
75+
public async Task Should_Use_Owner_Background_When_Not_Set()
76+
{
77+
var owner = new Window { Background = new SolidColorBrush(Colors.Blue) };
78+
owner.Show();
79+
80+
var dialog = new NativeWebDialog();
81+
dialog.Show(owner);
82+
83+
await WaitForAdapterCreation(dialog);
84+
85+
Assert.Null(dialog.DefaultBackground);
86+
Assert.Equal(Colors.Blue, GetAdapterBackground(dialog));
87+
Assert.Equal(Colors.Blue, GetWindowBackground(dialog));
88+
}
89+
90+
[AvaloniaFact]
91+
public async Task Should_Set_DefaultBackground_Before_Show()
92+
{
93+
var owner = new Window { Background = new SolidColorBrush(Colors.Blue) };
94+
owner.Show();
95+
96+
var dialog = new NativeWebDialog();
97+
dialog.DefaultBackground = Colors.Red;
98+
dialog.Show(owner);
99+
100+
await WaitForAdapterCreation(dialog);
101+
102+
// Explicit value wins over the owner background.
103+
Assert.Equal(Colors.Red, dialog.DefaultBackground);
104+
Assert.Equal(Colors.Red, GetAdapterBackground(dialog));
105+
Assert.Equal(Colors.Red, GetWindowBackground(dialog));
106+
}
107+
108+
[AvaloniaFact]
109+
public async Task Should_Set_DefaultBackground_After_Show()
110+
{
111+
var dialog = new NativeWebDialog();
112+
dialog.Show();
113+
114+
await WaitForAdapterCreation(dialog);
115+
116+
dialog.DefaultBackground = Colors.Red;
117+
Assert.Equal(Colors.Red, GetAdapterBackground(dialog));
118+
Assert.Equal(Colors.Red, GetWindowBackground(dialog));
119+
}
120+
121+
private static Color? GetAdapterBackground(NativeWebDialog dialog) =>
122+
((HeadlessWebViewAdapter)dialog.TryGetWebViewPlatformHandle()!).LastDefaultBackground;
123+
124+
private static Color? GetWindowBackground(NativeWebDialog dialog) =>
125+
(dialog.TryGetWindow()!.Background as ISolidColorBrush)?.Color;
126+
59127
[AvaloniaFact]
60128
public void Should_Expose_Platform_Handle()
61129
{

0 commit comments

Comments
 (0)