Skip to content

Commit

Permalink
fix: Do not allow reentrancy on UpdateLayout even with native layouting
Browse files Browse the repository at this point in the history
  • Loading branch information
dr1rrb committed Feb 3, 2021
1 parent cb1dd96 commit 7f11604
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 15 deletions.
72 changes: 59 additions & 13 deletions src/Uno.UI/UI/Xaml/UIElement.Layout.netstd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,39 @@ public void Measure(Size availableSize)
return;
}

if (IsVisualTreeRoot)
{
try
{
_isLayoutingVisualTreeRoot = true;
DoMeasure(availableSize);
}
finally
{
_isLayoutingVisualTreeRoot = false;
}
}
else
{
// If possible we avoid the try/finally which might be costly on some platforms
DoMeasure(availableSize);
}
}

private void DoMeasure(Size availableSize)
{
InvalidateArrange();

MeasureCore(availableSize);
LayoutInformation.SetAvailableSize(this, availableSize);
_isMeasureValid = true;
}

internal virtual void MeasureCore(Size availableSize)
{
throw new NotSupportedException("UIElement doesn't implement MeasureCore. Inherit from FrameworkElement, which properly implements MeasureCore.");
}

public void Arrange(Rect finalRect)
{
if (!(this is FrameworkElement))
Expand All @@ -119,27 +145,47 @@ public void Arrange(Rect finalRect)
return;
}

if (!_isArrangeValid || finalRect != LayoutSlot)
if (_isArrangeValid && finalRect == LayoutSlot)
{
ShowVisual();

// We must store the updated slot before natively arranging the element,
// so the updated value can be read by indirect code that is being invoked on arrange.
// For instance, the EffectiveViewPort computation reads that value to detect slot changes (cf. PropagateEffectiveViewportChange)
LayoutInformation.SetLayoutSlot(this, finalRect);
return;
}

ArrangeCore(finalRect);
_isArrangeValid = true;
if (IsVisualTreeRoot)
{
try
{
_isLayoutingVisualTreeRoot = true;
DoArrange(finalRect);
}
finally
{
_isLayoutingVisualTreeRoot = false;
}
}
else
{
// If possible we avoid the try/finally which might be costly on some platforms
DoArrange(finalRect);
}
}

private void DoArrange(Rect finalRect)
{
ShowVisual();

// We must store the updated slot before natively arranging the element,
// so the updated value can be read by indirect code that is being invoked on arrange.
// For instance, the EffectiveViewPort computation reads that value to detect slot changes (cf. PropagateEffectiveViewportChange)
LayoutInformation.SetLayoutSlot(this, finalRect);

ArrangeCore(finalRect);
_isArrangeValid = true;
}

partial void HideVisual();
partial void ShowVisual();

internal virtual void MeasureCore(Size availableSize)
{
throw new NotSupportedException("UIElement doesn't implement MeasureCore. Inherit from FrameworkElement, which properly implements MeasureCore.");
}


internal virtual void ArrangeCore(Rect finalRect)
{
Expand Down
12 changes: 10 additions & 2 deletions src/Uno.UI/UI/Xaml/UIElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public static void RegisterAsScrollPort(UIElement element)
/// </summary>
internal bool IsWindowRoot { get; set; }

/// <summary>
/// Is this view the top of the managed visual tree
/// </summary>
internal bool IsVisualTreeRoot { get; set; }

private void Initialize()
{
this.SetValue(KeyboardAcceleratorsProperty, new List<KeyboardAccelerator>(0), DependencyPropertyValuePrecedences.DefaultValue);
Expand Down Expand Up @@ -341,13 +346,16 @@ private void OpenContextFlyout(object sender, RightTappedRoutedEventArgs args)
internal bool IsRenderingSuspended { get; set; }

[ThreadStatic]
private static bool _isInUpdateLayout;
private static bool _isInUpdateLayout; // Currently within the UpdateLayout() method (explicit managed layout request)

[ThreadStatic]
private static bool _isLayoutingVisualTreeRoot; // Currenlty in Measure or Arrange of the element flagged with IsVisualTreeRoot (layout requested by the system)

private const int MaxLayoutIterations = 250;

public void UpdateLayout()
{
if (_isInUpdateLayout)
if (_isInUpdateLayout || _isLayoutingVisualTreeRoot)
{
return;
}
Expand Down
1 change: 1 addition & 0 deletions src/Uno.UI/UI/Xaml/Window.Skia.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ private void InternalSetContent(UIElement content)

_window = new Grid
{
IsVisualTreeRoot = true,
Children =
{
_rootBorder,
Expand Down
1 change: 1 addition & 0 deletions src/Uno.UI/UI/Xaml/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public UIElement Content
/// but also the PopupRoot, the DragRoot and all other internal UI elements.
/// On platforms like iOS and Android, we might still have few native controls above this.
/// </summary>
/// <remarks>This element is flagged with IsVisualTreeRoot.</remarks>
internal UIElement RootElement => InternalGetRootElement();

public Rect Bounds { get; private set; }
Expand Down

0 comments on commit 7f11604

Please sign in to comment.