Repository: unoplatform/uno (cs)

Issue:
Content Dialog - need update the overlay
### Current behavior 🐛

When open a Content Dialog with the App restored, and maximize, the overlay is not corret.
And after restore again it is not on the correct position.

![Image](https://github.com/user-attachments/assets/33139e40-c820-4259-a2ce-a8a024d1c183)


### Expected behavior 🎯

Overlay use the full window when resize (maximize).
Content Dialog stay centralized when restore.

### How to reproduce it (as minimally and precisely as possible) 🔬

Create a new UnoApp Blank

> dotnet new unoapp -o SampleTestVSCodeDev 

Add a Button to the Main Page.
```xaml
<Button Content="Click" Click="Button_Click" />
```

Add a dialog on the backend

```csharp


    private async Task Button_Click(object sender, RoutedEventArgs e)
    {
        var dialog = new ContentDialog
        {
            Title = "Hello World",
            Content = "This is a sample dialog.",
            CloseButtonText = "Close",
            XamlRoot = this.XamlRoot,
        };
        await dialog.ShowAsync();
    }
```

Or use this project

[SampleTestVSCodeDev.zip](https://github.com/user-attachments/files/20836875/SampleTestVSCodeDev.zip)

Run the App.
Click the button (dialog will open)
Maximize (overlay will break)
Restore (Dialog is not centralized)


See video

https://github.com/user-attachments/assets/539d5947-1d5d-45a7-9945-47152a8df890




### Workaround 🛠️

_No response_

### Renderer 🎨

- [ ] Skia
- [ ] Native

### Affected platforms 📱💻🖥️

_No response_

### Uno.Sdk version (and other relevant versions) 📦

Current, updated with dev  "Uno.Sdk": "6.1.0-dev.243"

### IDE version 🧑‍💻

_No response_

### Anything else we need to know? 💬

_No response_


Patch:
diff --git a/src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Window/AppleUIKitWindowWrapper.cs b/src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Window/AppleUIKitWindowWrapper.cs
index 5f866650a7db..6e43b672dc70 100644
--- a/src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Window/AppleUIKitWindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.AppleUIKit/UI/Xaml/Window/AppleUIKitWindowWrapper.cs
@@ -115,9 +115,7 @@ internal void RaiseNativeSizeChanged()
 	{
 		var newWindowSize = GetWindowSize();
 
-		Bounds = new Rect(default, newWindowSize);
-
-		SetVisibleBounds(_nativeWindow, newWindowSize);
+		SetBoundsAndVisibleBounds(new Rect(default, newWindowSize), GetVisibleBounds(_nativeWindow, newWindowSize));
 	}
 
 	private void ObserveOrientationAndSize()
@@ -156,7 +154,7 @@ internal Size GetWindowSize()
 		return new Size(nativeFrame.Width, nativeFrame.Height);
 	}
 
-	private void SetVisibleBounds(UIKit.UIWindow keyWindow, Windows.Foundation.Size windowSize)
+	private Rect GetVisibleBounds(UIKit.UIWindow keyWindow, Windows.Foundation.Size windowSize)
 	{
 		var windowBounds = new Windows.Foundation.Rect(default, windowSize);
 
@@ -186,7 +184,7 @@ private void SetVisibleBounds(UIKit.UIWindow keyWindow, Windows.Foundation.Size
 			height: windowBounds.Height - inset.Top - inset.Bottom
 		);
 
-		VisibleBounds = newVisibleBounds;
+		return newVisibleBounds;
 	}
 
 	private static bool UseSafeAreaInsets => UIDevice.CurrentDevice.CheckSystemVersion(11, 0);
diff --git a/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/UI/FrameBufferWindowWrapper.cs b/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/UI/FrameBufferWindowWrapper.cs
index ff3816452725..2c37cdda5fcf 100644
--- a/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/UI/FrameBufferWindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.Linux.FrameBuffer/UI/FrameBufferWindowWrapper.cs
@@ -23,8 +23,8 @@ public FrameBufferWindowWrapper()
 
 	internal void RaiseNativeSizeChanged(Size newWindowSize)
 	{
-		Bounds = new Rect(default, newWindowSize);
-		VisibleBounds = new Rect(default, newWindowSize);
+		var bounds = new Rect(default, newWindowSize);
+		SetBoundsAndVisibleBounds(bounds, bounds);
 		Size = new((int)newWindowSize.Width, (int)newWindowSize.Height);
 	}
 
diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Window/MacOSWindowWrapper.cs b/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Window/MacOSWindowWrapper.cs
index 7730a6ce1242..5b1bb59f42ee 100644
--- a/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Window/MacOSWindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.MacOS/UI/Xaml/Window/MacOSWindowWrapper.cs
@@ -77,9 +77,9 @@ private void OnHostPositionChanged(double x, double y)
 
 	private void OnHostSizeChanged(Size size)
 	{
+		var bounds = new Rect(default, size);
 		// in logical pixels
-		Bounds = new Rect(default, size);
-		VisibleBounds = Bounds;
+		SetBoundsAndVisibleBounds(bounds, bounds);
 		// in physical pixels
 		int w = (int)(size.Width * RasterizationScale);
 		int h = (int)(size.Height * RasterizationScale);
diff --git a/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/UI/Xaml/Window/WebAssemblyWindowWrapper.cs b/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/UI/Xaml/Window/WebAssemblyWindowWrapper.cs
index f81b93c2d3cc..a9b5908c9f32 100644
--- a/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/UI/Xaml/Window/WebAssemblyWindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.WebAssembly.Browser/UI/Xaml/Window/WebAssemblyWindowWrapper.cs
@@ -77,8 +77,8 @@ internal void RaiseNativeSizeChanged(Size newWindowSize)
 			Console.WriteLine($"RaiseNativeSizeChanged({newWindowSize.Width}, {newWindowSize.Height})");
 		}
 
-		Bounds = new Rect(default, newWindowSize);
-		VisibleBounds = new Rect(default, newWindowSize);
+		var bounds = new Rect(default, newWindowSize);
+		SetBoundsAndVisibleBounds(bounds, bounds);
 	}
 
 	internal void OnNativeVisibilityChanged(bool visible) => IsVisible = visible;
diff --git a/src/Uno.UI.Runtime.Skia.Win32/UI/Xaml/Window/Win32WindowWrapper.cs b/src/Uno.UI.Runtime.Skia.Win32/UI/Xaml/Window/Win32WindowWrapper.cs
index d7f149ad8773..1254e019fdc1 100644
--- a/src/Uno.UI.Runtime.Skia.Win32/UI/Xaml/Window/Win32WindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.Win32/UI/Xaml/Window/Win32WindowWrapper.cs
@@ -360,7 +360,8 @@ private void OnWindowSizeOrLocationChanged()
 		// same size regardless of the reported top-left corner by Windows.
 		// Bounds = new Rect(windowRect.left / scale, windowRect.top / scale, windowRect.Width / scale, windowRect.Height / scale);
 		// VisibleBounds = new Rect(clientRect.left / scale, clientRect.top / scale, clientRect.Width / scale, clientRect.Height / scale);
-		VisibleBounds = Bounds = new Rect(0, 0, clientRect.Width / scale, clientRect.Height / scale);
+		var bounds = new Rect(0, 0, clientRect.Width / scale, clientRect.Height / scale);
+		SetBoundsAndVisibleBounds(bounds, bounds);
 
 		Size = new SizeInt32(windowRect.Width, windowRect.Height);
 		Position = new PointInt32(windowRect.left, windowRect.top);
diff --git a/src/Uno.UI.Runtime.Skia.Wpf/UI/Xaml/Window/WpfWindowWrapper.cs b/src/Uno.UI.Runtime.Skia.Wpf/UI/Xaml/Window/WpfWindowWrapper.cs
index 6230c2693d91..cf022094197d 100644
--- a/src/Uno.UI.Runtime.Skia.Wpf/UI/Xaml/Window/WpfWindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.Wpf/UI/Xaml/Window/WpfWindowWrapper.cs
@@ -91,8 +91,8 @@ public override void ExtendContentIntoTitleBar(bool extend)
 
 	private void OnHostSizeChanged(Size size)
 	{
-		Bounds = new Windows.Foundation.Rect(default, new Windows.Foundation.Size(size.Width, size.Height));
-		VisibleBounds = Bounds;
+		var bounds = new Windows.Foundation.Rect(default, new Windows.Foundation.Size(size.Width, size.Height));
+		SetBoundsAndVisibleBounds(bounds, bounds);
 	}
 
 	private void OnNativeClosing(object? sender, CancelEventArgs e)
diff --git a/src/Uno.UI.Runtime.Skia.X11/UI/Xaml/Window/X11WindowWrapper.cs b/src/Uno.UI.Runtime.Skia.X11/UI/Xaml/Window/X11WindowWrapper.cs
index e21169be5bdf..213f445d0848 100644
--- a/src/Uno.UI.Runtime.Skia.X11/UI/Xaml/Window/X11WindowWrapper.cs
+++ b/src/Uno.UI.Runtime.Skia.X11/UI/Xaml/Window/X11WindowWrapper.cs
@@ -195,8 +195,8 @@ private void UpdatePositionAndSize()
 
 		var scale = _xamlRoot.RasterizationScale;
 		var newWindowSize = new Size(windowAttrs2.width / scale, windowAttrs2.height / scale);
-		Bounds = new Rect(default, newWindowSize);
-		VisibleBounds = new Rect(default, newWindowSize);
+		var bounds = new Rect(default, newWindowSize);
+		SetBoundsAndVisibleBounds(bounds, bounds);
 
 		// copy the root window dimensions to the top window
 		_ = XLib.XResizeWindow(display, _host.TopX11Window.Window, windowAttrs2.width, windowAttrs2.height);
diff --git a/src/Uno.UI.RuntimeTests/Helpers/ScreenHelper.cs b/src/Uno.UI.RuntimeTests/Helpers/ScreenHelper.cs
index a8e341981815..64ca35c345d5 100644
--- a/src/Uno.UI.RuntimeTests/Helpers/ScreenHelper.cs
+++ b/src/Uno.UI.RuntimeTests/Helpers/ScreenHelper.cs
@@ -5,6 +5,10 @@
 using Windows.Foundation;
 using Windows.UI.ViewManagement;
 using Microsoft.UI.Xaml;
+#if HAS_UNO
+using Uno.UI.Xaml;
+using Private.Infrastructure;
+#endif
 
 namespace Uno.UI.RuntimeTests.Helpers
 {
@@ -26,17 +30,17 @@ public static IDisposable OverrideVisibleBounds(Thickness unsafeArea, bool skipI
 			// Not supported on Windows
 			return null;
 #else
+			var visualTree = TestServices.WindowHelper.XamlRoot.VisualTree;
 			var fullBounds = Window.Current.Bounds;
-			var applicationView = ApplicationView.GetForCurrentView();
-			if (skipIfHasNativeUnsafeArea && fullBounds != applicationView.VisibleBounds)
+			if (skipIfHasNativeUnsafeArea && fullBounds != visualTree.VisibleBounds)
 			{
 				return null;
 			}
 			var height = fullBounds.Height - unsafeArea.Top - unsafeArea.Bottom;
 			var width = fullBounds.Width - unsafeArea.Left - unsafeArea.Right;
 			var overriddenVisibleBounds = new Rect(unsafeArea.Left, unsafeArea.Top, width, height);
-			applicationView.VisibleBoundsOverride = overriddenVisibleBounds;
-			return Disposable.Create(() => applicationView.VisibleBoundsOverride = null);
+			visualTree.VisibleBoundsOverride = overriddenVisibleBounds;
+			return Disposable.Create(() => visualTree.VisibleBoundsOverride = null);
 #endif
 		}
 
diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentDialog.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentDialog.cs
index 6e4d4ae1935c..7e88ddb7f2a3 100644
--- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentDialog.cs
+++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ContentDialog.cs
@@ -8,6 +8,7 @@
 using Uno.UI.RuntimeTests.Extensions;
 using Uno.UI.RuntimeTests.Helpers;
 using Windows.Foundation;
+using Windows.Graphics;
 using Windows.UI;
 using Windows.UI.Core;
 using Windows.UI.ViewManagement;
@@ -21,6 +22,8 @@
 using Microsoft.UI.Xaml.Automation.Peers;
 using MUXControlsTestApp.Utilities;
 using Combinatorial.MSTest;
+using Microsoft.UI.Windowing;
+using Private.Infrastructure;
 using Uno.UI.Extensions;
 
 namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls
@@ -728,6 +731,62 @@ public async Task When_Popup_Closed()
 		}
 #endif
 
+#if HAS_UNO
+		[TestMethod]
+		[GitHubWorkItem("https://github.com/unoplatform/uno/issues/20842")]
+		[PlatformCondition(ConditionMode.Exclude, RuntimeTestPlatforms.SkiaX11)]
+		public async Task When_Window_Resized()
+		{
+			if (!Uno.UI.Xaml.Controls.NativeWindowFactory.SupportsMultipleWindows)
+			{
+				Assert.Inconclusive("This test requires spawning a new window.");
+			}
+
+			var window = new Window();
+			try
+			{
+				bool activated = false;
+				window.Content = new Border();
+				window.Activated += (s, e) => activated = true;
+				window.Activate();
+				await TestServices.WindowHelper.WaitFor(() => activated);
+				var dialog = new ContentDialog
+				{
+					Title = "Hello World",
+					Content = "This is a sample dialog.",
+					CloseButtonText = "Close",
+					XamlRoot = window.Content.XamlRoot,
+				};
+				_ = dialog.ShowAsync();
+
+				await UITestHelper.WaitForIdle();
+				var popup = VisualTreeHelper.GetOpenPopupsForXamlRoot(window.Content.XamlRoot)[0];
+				var panel = (ContentDialog)popup.Child;
+				var size = panel.ActualSize;
+
+				var appWindow = window.AppWindow;
+				AppWindowChangedEventArgs args = null;
+				void OnChanged(AppWindow s, AppWindowChangedEventArgs e)
+				{
+					args = e;
+				}
+				appWindow.Changed += OnChanged;
+				var originalSize = appWindow.Size;
+
+				var adjustedSize = new SizeInt32 { Width = originalSize.Width + 10, Height = originalSize.Height + 10 };
+				appWindow.Resize(adjustedSize);
+				await TestServices.WindowHelper.WaitFor(() => args is not null);
+				Assert.IsTrue(args.DidSizeChange);
+				await UITestHelper.WaitForIdle();
+				Assert.AreNotEqual(size, panel.ActualSize);
+			}
+			finally
+			{
+				window.Close();
+			}
+		}
+#endif
+
 #if __ANDROID__
 		// Fails because keyboard does not appear when TextBox is programmatically focussed, or appearance is not correctly registered - https://github.com/unoplatform/uno/issues/7995
 		[Ignore()]
diff --git a/src/Uno.UI.Tests/Windows_UI_XAML_Controls/FlyoutTests/Given_Flyout.cs b/src/Uno.UI.Tests/Windows_UI_XAML_Controls/FlyoutTests/Given_Flyout.cs
index a171c9a9206e..44f48278a34e 100644
--- a/src/Uno.UI.Tests/Windows_UI_XAML_Controls/FlyoutTests/Given_Flyout.cs
+++ b/src/Uno.UI.Tests/Windows_UI_XAML_Controls/FlyoutTests/Given_Flyout.cs
@@ -185,8 +185,7 @@ public void When_Placement_Full()
 			var panel = flyout.GetPopupPanel();
 
 			var visibleBounds = new Rect(0, 0, 410, 815);
-			var applicationView = ApplicationView.GetForCurrentView();
-			using (applicationView.SetTemporaryVisibleBounds(visibleBounds))
+			using (SetTemporaryVisibleBounds(visibleBounds, button.XamlRoot))
 			{
 				panel.Measure(visibleBounds.Size);
 				panel.Arrange(visibleBounds);
@@ -229,8 +228,7 @@ public void When_Placement_Full_Max_Dims()
 			var panel = flyout.GetPopupPanel();
 
 			var visibleBounds = new Rect(0, 0, 410, 815);
-			var applicationView = ApplicationView.GetForCurrentView();
-			using (applicationView.SetTemporaryVisibleBounds(visibleBounds))
+			using (SetTemporaryVisibleBounds(visibleBounds, button.XamlRoot))
 			{
 				panel.Measure(visibleBounds.Size);
 				panel.Arrange(visibleBounds);
@@ -266,5 +264,12 @@ public void When_Xaml_And_Conflicting_ClassName()
 
 			app.HostView.Children.Add(SUT);
 		}
+
+		private IDisposable SetTemporaryVisibleBounds(Rect newVisibleBounds, XamlRoot xamlRoot)
+		{
+			xamlRoot.VisualTree.VisibleBoundsOverride = newVisibleBounds;
+
+			return new DisposableAction(() => xamlRoot.VisualTree.VisibleBoundsOverride = null);
+		}
 	}
 }
diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialog.Visuals.cs b/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialog.Visuals.cs
index acf80eccf779..42e8ef7dbcb7 100644
--- a/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialog.Visuals.cs
+++ b/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialog.Visuals.cs
@@ -226,7 +226,7 @@ private void SizeAndPositionContentInPopup()
 #if !HAS_UNO
 			var xamlRootSize = XamlRoot.Size;
 #else
-			var xamlRootSize = XamlRoot.VisualTree.TrueVisibleBounds.Size;
+			var xamlRootSize = XamlRoot.VisualTree.VisibleBounds.Size;
 #endif
 
 			var flowDirection = FlowDirection;
diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialogPopupPanel.cs b/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialogPopupPanel.cs
index 87960737b9c5..ad986a97e5bb 100644
--- a/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialogPopupPanel.cs
+++ b/src/Uno.UI/UI/Xaml/Controls/ContentDialog/ContentDialogPopupPanel.cs
@@ -64,7 +64,7 @@ private Size CalculateDialogAvailableSize(Size availableSize)
 				return availableSize;
 			}
 
-			var visibleBounds = XamlRoot?.VisualTree.TrueVisibleBounds ?? default;
+			var visibleBounds = XamlRoot?.VisualTree.VisibleBounds ?? default;
 
 			if (availableSize.Width > visibleBounds.Width)
 			{
@@ -80,7 +80,7 @@ private Size CalculateDialogAvailableSize(Size availableSize)
 
 		private Rect CalculateDialogPlacement(Size desiredSize, Size finalSize)
 		{
-			Rect visibleBounds = XamlRoot?.VisualTree.TrueVisibleBounds ?? default;
+			Rect visibleBounds = XamlRoot?.VisualTree.VisibleBounds ?? default;
 
 			var maximumWidth = Math.Min(visibleBounds.Width, finalSize.Width);
 			var maximumHeight = Math.Min(visibleBounds.Height, finalSize.Height);
diff --git a/src/Uno.UI/UI/Xaml/Internal/VisualTree.cs b/src/Uno.UI/UI/Xaml/Internal/VisualTree.cs
index 78768b3d4dbc..c7af58a48765 100644
--- a/src/Uno.UI/UI/Xaml/Internal/VisualTree.cs
+++ b/src/Uno.UI/UI/Xaml/Internal/VisualTree.cs
@@ -922,8 +922,6 @@ private static void VisualTreeNotFoundWarning()
 			}
 		}
 
-		internal void OnVisibleBoundChanged() => VisibleBoundsChanged?.Invoke(this, EventArgs.Empty);
-
 #if UNO_HAS_ENHANCED_LIFECYCLE
 		private bool IsMainVisualTree()
 			=> RootVisual != null;
diff --git a/src/Uno.UI/UI/Xaml/Internal/VisualTree.uno.cs b/src/Uno.UI/UI/Xaml/Internal/VisualTree.uno.cs
index 99cddadeadf3..4a3a6255061f 100644
--- a/src/Uno.UI/UI/Xaml/Internal/VisualTree.uno.cs
+++ b/src/Uno.UI/UI/Xaml/Internal/VisualTree.uno.cs
@@ -12,12 +12,14 @@ namespace Uno.UI.Xaml.Core;
 
 internal partial class VisualTree : IWeakReferenceProvider
 {
+	private Rect _visibleBounds;
+	private Rect? _visibleBoundsOverride;
+
 	private const int UnoTopZIndex = int.MaxValue - 100;
 	private const int FocusVisualZIndex = UnoTopZIndex + 1;
 	internal const int TextBoxTouchKnobPopupZIndex = FocusVisualZIndex + 1;
 
 	private ManagedWeakReference? _selfWeakReference;
-	private Windows.UI.ViewManagement.ApplicationView? _applicationView;
 
 	public Canvas? FocusVisualRoot { get; private set; }
 
@@ -37,102 +39,68 @@ public void EnsureFocusVisualRoot()
 	ManagedWeakReference IWeakReferenceProvider.WeakReference =>
 		_selfWeakReference ??= WeakReferencePool.RentSelfWeakReference(this);
 
-	//internal Size Size
-	//{
-	//	get
-	//	{
-	//		if (RootElement is XamlIsland xamlIsland)
-	//		{
-	//			// If the size is set explicitly, prefer this, as ActualSize property values may be
-	//			// a frame behind.
-	//			if (!double.IsNaN(xamlIsland.Width) && !double.IsNaN(xamlIsland.Height))
-	//			{
-	//				return new(xamlIsland.Width, xamlIsland.Height);
-	//			}
-
-	//			var actualSize = xamlIsland.ActualSize;
-	//			return new Size(actualSize.X, actualSize.Y);
-	//		}
-	//		else if (RootElement is RootVisual rootVisual)
-	//		{
-	//			if (Window.CurrentSafe is null)
-	//			{
-	//				throw new InvalidOperationException("Window.Current must be set.");
-	//			}
-
-	//			return Window.CurrentSafe.Bounds.Size;
-	//		}
-	//		else
-	//		{
-	//			throw new InvalidOperationException("Invalid VisualTree root type");
-	//		}
-	//	}
-	//}
-
 	internal Rect VisibleBounds
 	{
 		get
 		{
-			if (GetApplicationViewForOwnerWindow() is ApplicationView applicationView)
+			if (VisibleBoundsOverride is not null)
 			{
-				return applicationView.VisibleBounds;
+				return VisibleBoundsOverride.Value;
+			}
+			else if (Windows.ApplicationModel.Core.CoreApplication.IsFullFledgedApp)
+			{
+				return _visibleBounds;
 			}
 			else
 			{
-				var size = Size;
-				return new Rect(0, 0, size.Width, size.Height);
+				// For Uno islands, return the full size.
+				return new Rect(0, 0, Size.Width, Size.Height);
 			}
 		}
-	}
-
-	internal event EventHandler? VisibleBoundsChanged;
-
-	internal Rect TrueVisibleBounds
-	{
-		get
+		set
 		{
-			if (GetApplicationViewForOwnerWindow() is ApplicationView applicationView)
-			{
-				return applicationView.TrueVisibleBounds;
-			}
-			else
+			if (_visibleBounds != value)
 			{
-				var size = Size;
-				return new Rect(0, 0, size.Width, size.Height);
+				_visibleBounds = value;
+
+				OnVisibleBoundsChanged();
 			}
 		}
 	}
 
-	private ApplicationView? GetApplicationViewForOwnerWindow()
+	/// <summary>
+	/// If set, overrides the 'real' visible bounds. Used for testing visible bounds-related behavior on devices that have no native
+	/// 'unsafe area'.
+	/// </summary>
+	internal Rect? VisibleBoundsOverride
 	{
-		if (_applicationView is not null)
-		{
-			return _applicationView;
-		}
-
-		if (RootElement is XamlIslandRoot xamlIsland)
+		get => _visibleBoundsOverride;
+		set
 		{
-			if (xamlIsland.OwnerWindow is Window ownerWindow)
+			if (_visibleBoundsOverride != value)
 			{
-				_applicationView = ApplicationView.GetForWindowId(ownerWindow.AppWindow.Id);
-				return _applicationView;
+				_visibleBoundsOverride = value;
+
+				OnVisibleBoundsChanged();
 			}
 		}
-		else if (RootElement is RootVisual)
-		{
-			if (Window.CurrentSafe is null)
-			{
-				throw new InvalidOperationException("Window.Current must be set.");
-			}
+	}
+
+	internal event EventHandler? VisibleBoundsChanged;
 
-			_applicationView = ApplicationView.GetForCurrentViewSafe();
-			return _applicationView;
+	private void OnVisibleBoundsChanged()
+	{
+		VisibleBoundsChanged?.Invoke(this, EventArgs.Empty);
+
+		if (RootElement is XamlIslandRoot { OwnerWindow: { } ownerWindow } _)
+		{
+			// Notify the XamlIslandRoot that the visible bounds have changed.
+			ApplicationView.GetOrCreateForWindowId(ownerWindow.AppWindow.Id).SetVisibleBounds(VisibleBounds);
 		}
-		else
+		else if (RootElement is RootVisual window)
 		{
-			throw new InvalidOperationException("Invalid VisualTree root type");
+			// Notify the Window that the visible bounds have changed.
+			ApplicationView.GetForCurrentViewSafe().SetVisibleBounds(VisibleBounds);
 		}
-
-		return null;
 	}
 }
diff --git a/src/Uno.UI/UI/Xaml/Window/Implementations/BaseWindowImplementation.cs b/src/Uno.UI/UI/Xaml/Window/Implementations/BaseWindowImplementation.cs
index 26111252d147..db25b0bc58b7 100644
--- a/src/Uno.UI/UI/Xaml/Window/Implementations/BaseWindowImplementation.cs
+++ b/src/Uno.UI/UI/Xaml/Window/Implementations/BaseWindowImplementation.cs
@@ -198,8 +198,10 @@ private void OnNativeSizeChanged(object? sender, Size size)
 
 	private void SetVisibleBoundsFromNative()
 	{
-		ApplicationView.GetForWindowId(Window.AppWindow.Id).SetVisibleBounds(NativeWindowWrapper?.VisibleBounds ?? default);
-		XamlRoot?.VisualTree?.OnVisibleBoundChanged();
+		if (XamlRoot?.VisualTree is { } visualTree && NativeWindowWrapper is { } wrapper)
+		{
+			visualTree.VisibleBounds = wrapper.VisibleBounds;
+		}
 	}
 
 	protected virtual void OnSizeChanged(Size newSize) { }
diff --git a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.Android.cs b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.Android.cs
index c4d5bebae7ed..3cab13980894 100644
--- a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.Android.cs
+++ b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.Android.cs
@@ -81,8 +81,7 @@ internal void RaiseNativeSizeChanged()
 	{
 		var (windowSize, visibleBounds) = GetVisualBounds();
 
-		Bounds = new Rect(default, windowSize);
-		VisibleBounds = visibleBounds;
+		SetBoundsAndVisibleBounds(new Rect(default, windowSize), visibleBounds);
 		Size = new((int)(windowSize.Width * RasterizationScale), (int)(windowSize.Height * RasterizationScale));
 		ApplySystemOverlaysTheming();
 
diff --git a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.UIKit.cs b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.UIKit.cs
index 54ae95e30f90..d79004635789 100644
--- a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.UIKit.cs
+++ b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.UIKit.cs
@@ -70,10 +70,8 @@ internal void RaiseNativeSizeChanged()
 	{
 		var newWindowSize = GetWindowSize();
 
-		Bounds = new Rect(default, newWindowSize);
+		SetBoundsAndVisibleBounds(new Rect(default, newWindowSize), GetVisibleBounds(_nativeWindow, newWindowSize));
 		Size = new((int)(newWindowSize.Width * RasterizationScale), (int)(newWindowSize.Height * RasterizationScale));
-
-		SetVisibleBounds(_nativeWindow, newWindowSize);
 	}
 
 	private void ObserveOrientationAndSize()
@@ -112,7 +110,7 @@ public override Size GetWindowSize()
 		return new Size(nativeFrame.Width, nativeFrame.Height);
 	}
 
-	private void SetVisibleBounds(UIWindow keyWindow, Size windowSize)
+	private Rect GetVisibleBounds(UIWindow keyWindow, Size windowSize)
 	{
 		var windowBounds = new Rect(default, windowSize);
 
@@ -140,7 +138,7 @@ private void SetVisibleBounds(UIWindow keyWindow, Size windowSize)
 			height: windowBounds.Height - inset.Top - inset.Bottom
 		);
 
-		VisibleBounds = newVisibleBounds;
+		return newVisibleBounds;
 	}
 
 	private static bool UseSafeAreaInsets => UIDevice.CurrentDevice.CheckSystemVersion(11, 0);
diff --git a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.wasm.cs b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.wasm.cs
index 508fd8ad5730..db316f99fc5f 100644
--- a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.wasm.cs
+++ b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.wasm.cs
@@ -38,8 +38,7 @@ internal void RaiseNativeSizeChanged(double width, double height)
 	{
 		var bounds = new Rect(default, new Size(width, height));
 
-		Bounds = bounds;
-		VisibleBounds = bounds;
+		SetBoundsAndVisibleBounds(bounds, bounds);
 		Size = new((int)(bounds.Width * RasterizationScale), (int)(bounds.Height * RasterizationScale));
 	}
 
diff --git a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapperBase.cs b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapperBase.cs
index b67ef84046d2..1a2341b645ae 100644
--- a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapperBase.cs
+++ b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapperBase.cs
@@ -9,6 +9,7 @@
 using Windows.Foundation;
 using Windows.Graphics;
 using Windows.UI.Core;
+using Windows.UI.ViewManagement;
 
 #if HAS_UNO_WINUI
 using Microsoft.UI.Dispatching;
@@ -91,6 +92,26 @@ public Rect VisibleBounds
 		}
 	}
 
+	/// <summary>
+	/// The same as setting <see cref="VisibleBounds"/>, <see cref="Bounds"/> and <see cref="Size"/> but makes sure the
+	/// fired events are fired only after both properties are updated "atomically"
+	/// </summary>
+	public void SetBoundsAndVisibleBounds(Rect bounds, Rect visibleBounds)
+	{
+		if (_visibleBounds != visibleBounds)
+		{
+			_visibleBounds = visibleBounds;
+			VisibleBoundsChanged?.Invoke(this, visibleBounds);
+		}
+
+		if (_bounds != bounds)
+		{
+			_bounds = bounds;
+			SizeChanged?.Invoke(this, bounds.Size);
+			RaiseContentIslandStateChanged(ContentIslandStateChangedEventArgs.ActualSizeChange);
+		}
+	}
+
 	public CoreWindowActivationState ActivationState
 	{
 		get => _activationState;
diff --git a/src/Uno.UWP/UI/ViewManagement/ApplicationView.cs b/src/Uno.UWP/UI/ViewManagement/ApplicationView.cs
index 8c7e8a088b26..b71bfd92c702 100644
--- a/src/Uno.UWP/UI/ViewManagement/ApplicationView.cs
+++ b/src/Uno.UWP/UI/ViewManagement/ApplicationView.cs
@@ -34,6 +34,8 @@ public partial class ApplicationView
 		private IReadOnlyList<Rect> _defaultSpanningRects;
 		private IApplicationViewSpanningRects _applicationViewSpanningRects;
 
+		private Rect _visibleBounds;
+
 		[global::Uno.NotImplemented]
 		public int Id => 1;
 
@@ -75,32 +77,7 @@ public bool SetDesiredBoundsMode(global::Windows.UI.ViewManagement.ApplicationVi
 			return true;
 		}
 
-		private Rect _visibleBounds;
-		public Foundation.Rect VisibleBounds { get => VisibleBoundsOverride ?? _visibleBounds; private set => _visibleBounds = value; }
-
-		/// <summary>
-		/// All other platforms: equivalent to <see cref="VisibleBounds"/>.
-		///
-		/// Android: returns the visible bounds taking the status bar into account. The status bar is not removed from <see cref="VisibleBounds"/>
-		/// on Android when it's opaque, on the grounds that the root managed view is already arranged below the status bar in y-direction by
-		/// default (unlike iOS), but in some cases the correct total height is needed, hence this property.
-		/// </summary>
-		internal Rect TrueVisibleBounds =>
-#if __ANDROID__
-			VisibleBoundsOverride ?? _trueVisibleBounds;
-#else
-			VisibleBounds;
-#endif
-
-		/// <summary>
-		/// If set, overrides the 'real' visible bounds. Used for testing visible bounds-related behavior on devices that have no native
-		/// 'unsafe area'.
-		/// </summary>
-		internal Rect? VisibleBoundsOverride
-		{
-			get;
-			set;
-		}
+		public Foundation.Rect VisibleBounds => _visibleBounds;
 
 		public event global::Windows.Foundation.TypedEventHandler<global::Windows.UI.ViewManagement.ApplicationView, object> VisibleBoundsChanged;
 
@@ -256,17 +233,11 @@ private void TryInitializeSpanningRectsExtension()
 			}
 		}
 
-		internal void SetVisibleBounds(Rect newVisibleBounds)
+		internal void SetVisibleBounds(Rect visibleBounds)
 		{
-			if (newVisibleBounds != VisibleBounds)
+			if (_visibleBounds != visibleBounds)
 			{
-				VisibleBounds = newVisibleBounds;
-
-				if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug))
-				{
-					this.Log().Debug($"Updated visible bounds {VisibleBounds}");
-				}
-
+				_visibleBounds = visibleBounds;
 				VisibleBoundsChanged?.Invoke(this, null);
 			}
 		}


Test patch:
diff --git a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.unittests.cs b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.unittests.cs
index 2c62d30958a6..d9bea4be94a1 100644
--- a/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.unittests.cs
+++ b/src/Uno.UI/UI/Xaml/Window/Native/NativeWindowWrapper.unittests.cs
@@ -28,8 +28,7 @@ internal void RaiseNativeSizeChanged(double width, double height)
 	{
 		var bounds = new Rect(default, new Size(width, height));
 
-		Bounds = bounds;
-		VisibleBounds = bounds;
+		SetBoundsAndVisibleBounds(bounds, bounds);
 		Size = new((int)(bounds.Width * RasterizationScale), (int)(bounds.Height * RasterizationScale));
 	}
 }
diff --git a/src/Uno.UWP/UI/ViewManagement/ApplicationView.unittests.cs b/src/Uno.UWP/UI/ViewManagement/ApplicationView.unittests.cs
deleted file mode 100644
index b57c2a967435..000000000000
--- a/src/Uno.UWP/UI/ViewManagement/ApplicationView.unittests.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-﻿#if IS_UNIT_TESTS
-using System;
-using System.Collections.Generic;
-using System.Text;
-using Uno;
-using Uno.Extensions;
-using Uno.Foundation.Logging;
-using Windows.Foundation;
-
-namespace Windows.UI.ViewManagement
-{
-	partial class ApplicationView
-	{
-		internal IDisposable SetTemporaryVisibleBounds(Rect newVisibleBounds)
-		{
-			var old = VisibleBounds;
-			VisibleBounds = newVisibleBounds;
-
-			return new DisposableAction(() => VisibleBounds = old);
-		}
-	}
-}
-#endif