Browse Source

Merge pull request #8880 from AvaloniaUI/fixes/8878-mac-window-order

macOS: Fix child window order with multiple child windows
pull/8920/head
Max Katz 4 years ago
committed by GitHub
parent
commit
df57c9521d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  2. 27
      samples/IntegrationTestApp/MacOSIntegration.cs
  3. 13
      samples/IntegrationTestApp/MainWindow.axaml.cs
  4. 8
      samples/IntegrationTestApp/ShowWindowTest.axaml
  5. 28
      samples/IntegrationTestApp/ShowWindowTest.axaml.cs
  6. 89
      tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

2
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -91,8 +91,6 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
if(_parent != nullptr)
{
_parent->_children.remove(this);
_parent->BringToFront();
}
auto cparent = dynamic_cast<WindowImpl *>(parent);

27
samples/IntegrationTestApp/MacOSIntegration.cs

@ -0,0 +1,27 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Controls;
namespace IntegrationTestApp
{
public static class MacOSIntegration
{
[DllImport("/usr/lib/libobjc.dylib", EntryPoint = "sel_registerName")]
private static extern IntPtr GetHandle(string name);
[DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
private static extern long Int64_objc_msgSend(IntPtr receiver, IntPtr selector);
private static readonly IntPtr s_orderedIndexSelector;
static MacOSIntegration()
{
s_orderedIndexSelector = GetHandle("orderedIndex");;
}
public static long GetOrderedIndex(Window window)
{
return Int64_objc_msgSend(window.PlatformImpl!.Handle.Handle, s_orderedIndexSelector);
}
}
}

13
samples/IntegrationTestApp/MainWindow.axaml.cs

@ -1,11 +1,13 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
using Microsoft.CodeAnalysis;
namespace IntegrationTestApp
{
@ -63,6 +65,17 @@ namespace IntegrationTestApp
WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex,
};
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
// Make sure the windows have unique names and AutomationIds.
var existing = lifetime.Windows.OfType<ShowWindowTest>().Count();
if (existing > 0)
{
AutomationProperties.SetAutomationId(window, window.Name + (existing + 1));
window.Title += $" {existing + 1}";
}
}
if (size.HasValue)
{
window.Width = size.Value.Width;

8
samples/IntegrationTestApp/ShowWindowTest.axaml

@ -3,7 +3,7 @@
x:Class="IntegrationTestApp.ShowWindowTest"
Name="SecondaryWindow"
Title="Show Window Test">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<Label Grid.Column="0" Grid.Row="1">Client Size</Label>
<TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"
Text="{Binding ClientSize, Mode=OneWay}"/>
@ -31,6 +31,10 @@
<ComboBoxItem>Maximized</ComboBoxItem>
<ComboBoxItem>Fullscreen</ComboBoxItem>
</ComboBox>
<Button Name="HideButton" Grid.Row="8" Command="{Binding $parent[Window].Hide}">Hide</Button>
<Label Grid.Column="0" Grid.Row="8">Order (mac)</Label>
<TextBox Name="Order" Grid.Column="1" Grid.Row="8" IsReadOnly="True"/>
<Button Name="HideButton" Grid.Row="9" Command="{Binding $parent[Window].Hide}">Hide</Button>
</Grid>
</Window>

28
samples/IntegrationTestApp/ShowWindowTest.axaml.cs

@ -1,21 +1,32 @@
using System;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Rendering;
using Avalonia.Threading;
namespace IntegrationTestApp
{
public class ShowWindowTest : Window
{
private readonly DispatcherTimer? _timer;
private readonly TextBox? _orderTextBox;
public ShowWindowTest()
{
InitializeComponent();
DataContext = this;
PositionChanged += (s, e) => this.GetControl<TextBox>("Position").Text = $"{Position}";
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
_orderTextBox = this.GetControl<TextBox>("Order");
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
_timer.Tick += TimerOnTick;
_timer.Start();
}
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
@ -36,5 +47,16 @@ namespace IntegrationTestApp
ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}";
}
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_timer?.Stop();
}
private void TimerOnTick(object? sender, EventArgs e)
{
_orderTextBox!.Text = MacOSIntegration.GetOrderedIndex(this).ToString();
}
}
}

89
tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs

@ -30,7 +30,7 @@ namespace Avalonia.IntegrationTests.Appium
tab.Click();
return;
}
catch (WebDriverException e) when (retry++ < 3)
catch (WebDriverException) when (retry++ < 3)
{
// MacOS sometimes seems to need a bit of time to get itself back in order after switching out
// of fullscreen.
@ -49,19 +49,16 @@ namespace Avalonia.IntegrationTests.Appium
{
mainWindow.Click();
var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow"));
var mainWindowIndex = GetWindowOrder(windows, "MainWindow");
var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow");
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
Assert.Equal(0, secondaryWindowIndex);
Assert.Equal(1, mainWindowIndex);
Assert.Equal(1, secondaryWindowIndex);
}
}
[PlatformFact(TestPlatforms.MacOS)]
public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip()
{
var mainWindow = FindWindow(_session, "MainWindow");
var mainWindow = GetWindow("MainWindow");
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual))
{
@ -70,24 +67,21 @@ namespace Avalonia.IntegrationTests.Appium
.ClickAndHold()
.Perform();
var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow"));
var mainWindowIndex = GetWindowOrder(windows, "MainWindow");
var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow");
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
new Actions(_session)
.MoveToElement(mainWindow, 100, 1)
.Release()
.Perform();
Assert.Equal(0, secondaryWindowIndex);
Assert.Equal(1, mainWindowIndex);
Assert.Equal(1, secondaryWindowIndex);
}
}
[PlatformFact(TestPlatforms.MacOS)]
public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen()
{
var mainWindow = FindWindow(_session, "MainWindow");
var mainWindow = GetWindow("MainWindow");
var buttons = mainWindow.GetChromeButtons();
buttons.maximize.Click();
@ -98,14 +92,8 @@ namespace Avalonia.IntegrationTests.Appium
{
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.Manual))
{
var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow"));
var mainWindowIndex = GetWindowOrder(windows, "MainWindow");
var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow");
Assert.Equal(0, secondaryWindowIndex);
Assert.Equal(1, mainWindowIndex);
Thread.Sleep(5000);
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
Assert.Equal(1, secondaryWindowIndex);
}
}
finally
@ -122,13 +110,8 @@ namespace Avalonia.IntegrationTests.Appium
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.Manual))
{
mainWindow.Click();
var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow"));
var mainWindowIndex = GetWindowOrder(windows, "MainWindow");
var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow");
Assert.Equal(0, secondaryWindowIndex);
Assert.Equal(1, mainWindowIndex);
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
Assert.Equal(1, secondaryWindowIndex);
}
}
@ -141,22 +124,35 @@ namespace Avalonia.IntegrationTests.Appium
{
mainWindow.Click();
var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow"));
var mainWindowIndex = GetWindowOrder(windows, "MainWindow");
var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow");
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
Assert.Equal(1, secondaryWindowIndex);
Assert.Equal(0, mainWindowIndex);
Assert.Equal(2, secondaryWindowIndex);
var sendToBack = _session.FindElementByAccessibilityId("SendToBack");
sendToBack.Click();
}
}
[PlatformFact(TestPlatforms.MacOS)]
public void WindowOrder_Owned_Is_Correct_After_Closing_Window()
{
using (OpenWindow(new PixelSize(300, 500), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner))
{
// Open a second child window, and close it.
using (OpenWindow(new PixelSize(200, 200), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner))
{
}
var secondaryWindowIndex = GetWindowOrder("SecondaryWindow");
Assert.Equal(1, secondaryWindowIndex);
}
}
[PlatformFact(TestPlatforms.MacOS)]
public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown()
{
var window = FindWindow(_session, "MainWindow");
var window = GetWindow("MainWindow");
var (closeButton, miniaturizeButton, zoomButton) = window.GetChromeButtons();
Assert.True(closeButton.Enabled);
@ -176,7 +172,7 @@ namespace Avalonia.IntegrationTests.Appium
{
using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner))
{
var secondaryWindow = FindWindow(_session, "SecondaryWindow");
var secondaryWindow = GetWindow("SecondaryWindow");
var (closeButton, miniaturizeButton, zoomButton) = secondaryWindow.GetChromeButtons();
Assert.True(closeButton.Enabled);
@ -192,7 +188,7 @@ namespace Avalonia.IntegrationTests.Appium
{
using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
{
var secondaryWindow = FindWindow(_session, "SecondaryWindow");
var secondaryWindow = GetWindow("SecondaryWindow");
var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons();
miniaturizeButton.Click();
@ -220,7 +216,7 @@ namespace Avalonia.IntegrationTests.Appium
// causes Appium to think it's a different window.
OpenWindow(null, ShowWindowMode.Owned, WindowStartupLocation.Manual);
var secondaryWindow = FindWindow(_session, "SecondaryWindow");
var secondaryWindow = GetWindow("SecondaryWindow");
var hideButton = secondaryWindow.FindElementByAccessibilityId("HideButton");
hideButton.Click();
@ -236,7 +232,7 @@ namespace Avalonia.IntegrationTests.Appium
_session.FindElementByAccessibilityId("RestoreAll").Click();
// Close the window manually.
secondaryWindow = FindWindow(_session, "SecondaryWindow");
secondaryWindow = GetWindow("SecondaryWindow");
secondaryWindow.GetChromeButtons().close.Click();
}
@ -259,18 +255,19 @@ namespace Avalonia.IntegrationTests.Appium
return showButton.OpenWindowWithClick();
}
private static int GetWindowOrder(IReadOnlyCollection<AppiumWebElement> elements, string identifier)
private AppiumWebElement GetWindow(string identifier)
{
return elements.TakeWhile(x =>
x.FindElementByXPath("XCUIElementTypeWindow")?.GetAttribute("identifier") != identifier).Count();
// The Avalonia a11y tree currently exposes two nested Window elements, this is a bug and should be fixed
// but in the meantime use the `parent::' selector to return the parent "real" window.
return _session.FindElementByXPath(
$"XCUIElementTypeWindow//*[@identifier='{identifier}']/parent::XCUIElementTypeWindow");
}
private static AppiumWebElement FindWindow(AppiumDriver<AppiumWebElement> session, string identifier)
private int GetWindowOrder(string identifier)
{
var windows = session.FindElementsByXPath("XCUIElementTypeWindow");
return windows.First(x =>
x.FindElementsByXPath("XCUIElementTypeWindow")
.Any(y => y.GetAttribute("identifier") == identifier));
var window = GetWindow(identifier);
var order = window.FindElementByXPath("//*[@identifier='Order']");
return int.Parse(order.Text);
}
public enum ShowWindowMode

Loading…
Cancel
Save