Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ios/catalyst] fix leak in NavigationPage #22810

Merged
merged 2 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -674,11 +674,11 @@ void RemovePage(Page page)
void RemoveViewControllers(bool animated)
{
var controller = TopViewController as ParentingViewController;
if (controller == null || controller.Child == null || controller.Child.Handler == null)
if (controller?.Child is not Page child || child.Handler == null)
return;

// Gesture in progress, lets not be proactive and just wait for it to finish
var task = GetAppearedOrDisappearedTask(controller.Child);
var task = GetAppearedOrDisappearedTask(child);

task.ContinueWith(t =>
{
Expand Down Expand Up @@ -1113,7 +1113,7 @@ class ParentingViewController : UIViewController
{
readonly WeakReference<NavigationRenderer> _navigation;

Page _child;
WeakReference<Page> _child;
bool _disposed;
ToolbarTracker _tracker = new ToolbarTracker();

Expand All @@ -1128,19 +1128,25 @@ public ParentingViewController(NavigationRenderer navigation)

public Page Child
{
get { return _child; }
get => _child?.GetTargetOrDefault();
set
{
if (_child == value)
var child = Child;
if (child == value)
return;

if (_child != null)
_child.PropertyChanged -= HandleChildPropertyChanged;

_child = value;
if (child is not null)
child.PropertyChanged -= HandleChildPropertyChanged;

if (_child != null)
_child.PropertyChanged += HandleChildPropertyChanged;
if (value is not null)
{
_child = new(value);
value.PropertyChanged += HandleChildPropertyChanged;
}
else
{
_child = null;
}

UpdateHasBackButton();
UpdateLargeTitles();
Expand Down Expand Up @@ -1228,7 +1234,6 @@ public override void ViewDidLoad()

_tracker.Target = Child;
_tracker.AdditionalTargets = Child.GetParentPages();
_tracker.CollectionChanged += TrackerOnCollectionChanged;

UpdateToolbarItems();
}
Expand All @@ -1247,6 +1252,20 @@ public override void ViewWillAppear(bool animated)
base.ViewWillAppear(animated);
}

public override void WillMoveToParentViewController(UIViewController parent)
{
base.WillMoveToParentViewController(parent);

if (parent is null)
{
_tracker.CollectionChanged -= TrackerOnCollectionChanged;
}
else
{
_tracker.CollectionChanged += TrackerOnCollectionChanged;
}
}

protected override void Dispose(bool disposing)
{
if (_disposed)
Expand All @@ -1258,10 +1277,10 @@ protected override void Dispose(bool disposing)

if (disposing)
{
if (Child != null)
if (Child is Page child)
{
Child.SendDisappearing();
Child.PropertyChanged -= HandleChildPropertyChanged;
child.SendDisappearing();
child.PropertyChanged -= HandleChildPropertyChanged;
Child = null;
}

Expand Down Expand Up @@ -1517,17 +1536,17 @@ void TrackerOnCollectionChanged(object sender, EventArgs eventArgs)

void UpdateHasBackButton()
{
if (Child == null || NavigationItem.HidesBackButton == !NavigationPage.GetHasBackButton(Child))
if (Child is not Page child || NavigationItem.HidesBackButton == !NavigationPage.GetHasBackButton(child))
return;

NavigationItem.HidesBackButton = !NavigationPage.GetHasBackButton(Child);
NavigationItem.HidesBackButton = !NavigationPage.GetHasBackButton(child);

NavigationRenderer n;
if (!_navigation.TryGetTarget(out n))
return;

if (!(OperatingSystem.IsIOSVersionAtLeast(11) || OperatingSystem.IsMacCatalystVersionAtLeast(11)) || n._parentFlyoutPage != null)
UpdateTitleArea(Child);
UpdateTitleArea(child);
}

void UpdateNavigationBarVisibility(bool animated)
Expand Down
39 changes: 31 additions & 8 deletions src/Controls/src/Core/Menu/MenuItemTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ internal abstract class MenuItemTracker<TMenuItem>
where TMenuItem : BaseMenuItem
{
int _flyoutDetails;
Page _target;
WeakReference<Page> _target;
List<WeakReference<Page>> _additionalTargets = new();
public MenuItemTracker()
{
}
Expand All @@ -21,7 +22,28 @@ public MenuItemTracker()

protected abstract IComparer<TMenuItem> CreateComparer();

public IEnumerable<Page> AdditionalTargets { get; set; }
public IEnumerable<Page> AdditionalTargets
{
get
{
foreach (var target in _additionalTargets)
{
if (target.TryGetTarget(out var page))
yield return page;
}
}
set
{
_additionalTargets.Clear();
if (value is not null)
{
foreach (var page in value)
{
_additionalTargets.Add(new(page));
}
}
}
}

public bool HaveFlyoutPage
{
Expand All @@ -32,17 +54,18 @@ public bool HaveFlyoutPage

public Page Target
{
get { return _target; }
get => _target is not null && _target.TryGetTarget(out var target) ? target : null;
set
{
if (_target == value)
var target = Target;
if (target == value)
return;

UntrackTarget(_target);
_target = value;
UntrackTarget(target);
_target = value is null ? null : new(value);

if (_target != null)
TrackTarget(_target);
if (value != null)
TrackTarget(value);
EmitCollectionChanged();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,37 @@ await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(navPage), async
});
}

[Fact(DisplayName = "NavigationPage Does Not Leak")]
[Fact(DisplayName = "Does Not Leak"
#if WINDOWS
, Skip = "Failing"
#endif
Comment on lines +307 to +309
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't figure out Windows yet:

image

So I think it's related to one of these lines:

navigationFrame.Navigated += OnNavigated;

fe.OnLoaded(FirePendingNavigationFinished);

But it's also possible this only fails in tests and not real apps.

)]
public async Task DoesNotLeak()
{
SetupBuilder();
WeakReference pageReference = null;
WeakReference handlerReference = null;

{
var navPage = new NavigationPage(new ContentPage());
var window = new Window(navPage);

await CreateHandlerAndAddToWindow<WindowHandlerStub>(window, (handler) =>
{
pageReference = new WeakReference(navPage);
handlerReference = new WeakReference(handler);

// Just replace the page with a new one
window.Page = new ContentPage();
});
}

await AssertionExtensions.WaitForGC(pageReference, handlerReference);
}

[Fact(DisplayName = "Child Pages Do Not Leak")]
public async Task ChildPagesDoNotLeak()
{

#if ANDROID
if (!OperatingSystem.IsAndroidVersionAtLeast(30))
Expand Down
Loading