Skip to content

Ensure system back event is passed to child page once - #21246

Merged
MrJul merged 8 commits into
masterfrom
page_navigation_back_handler
May 13, 2026
Merged

Ensure system back event is passed to child page once#21246
MrJul merged 8 commits into
masterfrom
page_navigation_back_handler

Conversation

@emmauss

@emmauss emmauss commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

What does the pull request do?

This PR changes the routing strategy for PageNavigationSystemBackButtonPressedEvent, and updates handlers for all current page types. Pages that host other pages will generate an event for that child page and raise it. This fixes a StackOverflow that occurs on pages hosted in a PageNavigationHost.

What is the current behavior?

When Back system buttons is pressed, PageNavigationHost generates a PageNavigationSystemBackButtonPressedEvent and raises it on the child page. The global Page handler for this event receives it and raises it again on the current page's child page. Because PageNavigationSystemBackButtonPressedEvent routing strategy is Bubble, this event is raised on the parent again if the child doesn't handle it. The global Page handler grabs this and raises it again on the child. This causes a closed loop and ends up in a StackOverflow.

What is the updated/expected behavior with this PR?

The global Page handler for PageNavigationSystemBackButtonPressedEvent is removed. ContentPage, which is the only non-hosting Page will not forward this event to CurrentPage, since it has no logic to set that property. Other pages that host pages will check if they have special behavior when back is requested, like closing the drawer in DrawerPage, before raising the event on their CurrentPage.

How was the solution implemented (if it's not obvious)?

Checklist

Breaking changes

Obsoletions / Deprecations

Fixed issues

@emmauss
emmauss requested review from MrJul and jsuarezruiz April 22, 2026 18:14
@emmauss emmauss added bug backport-candidate-12.0.x Consider this PR for backporting to 12.0 branch labels Apr 22, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates how PageNavigationSystemBackButtonPressedEvent is routed and handled across Avalonia Page types to prevent recursive event forwarding (and resulting StackOverflow) when pages are hosted inside PageNavigationHost.

Changes:

  • Changed PageNavigationSystemBackButtonPressedEvent routing strategy from Bubble to Direct and removed the global forwarding class handler on Page.
  • Added per-page class handlers to host pages (e.g., TabbedPage, CarouselPage, DrawerPage) to perform page-specific back behavior and then forward to CurrentPage once.
  • Added a ContentPage handler to invoke OnSystemBackButtonPressed() without forwarding.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Avalonia.Controls/Page/TabbedPage.cs Adds a class handler to handle/forward system back to CurrentPage.
src/Avalonia.Controls/Page/Page.cs Switches system-back routed event to Direct and removes the base class forwarding handler.
src/Avalonia.Controls/Page/DrawerPage.cs Updates system-back handler to early-exit when handled, handle drawer behavior, then forward to CurrentPage.
src/Avalonia.Controls/Page/ContentPage.cs Adds a system-back handler that invokes OnSystemBackButtonPressed() only.
src/Avalonia.Controls/Page/CarouselPage.cs Adds a class handler to handle/forward system back to CurrentPage.

Comment thread src/Avalonia.Controls/Page/Page.cs
Comment thread src/Avalonia.Controls/Page/DrawerPage.cs Outdated
Comment thread src/Avalonia.Controls/Page/TabbedPage.cs Outdated
Comment thread src/Avalonia.Controls/Page/CarouselPage.cs Outdated
@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065007-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065011-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@emmauss

emmauss commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

Added tests

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065015-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@jsuarezruiz jsuarezruiz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The fix direction looks good overall, especially changing the forwarded child raises to use fresh RoutedEventArgs.

One quesiton: the base Page class handler added back in the last commit now runs alongside the concrete class handlers. Since Direct routed events still invoke all matching class handlers for the sender type, a ContentPage, TabbedPage, CarouselPage, or DrawerPage matches both Page and its concrete handler. So, when OnSystemBackButtonPressed() returns false (default/common path) the Handled guard does not stop a second call, so, would observe two invocations? Can you verify that behavior?

@emmauss

emmauss commented Apr 23, 2026

Copy link
Copy Markdown
Contributor Author

Yes. It would be called multiple times

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065026-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

{
PageNavigationSystemBackButtonPressedEvent.AddClassHandler<Page>((page, args) =>
AffectsMeasure<Page>(SafeAreaPaddingProperty);
PageNavigationSystemBackButtonPressedEvent.AddClassHandler<Page>((sender, eventArgs) =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This removes the generic CurrentPage forwarding behavior from Page and replaces it with per-host forwarding in the built-in page containers. That fixes the immediate recursion problem, but it also changes the contract for custom Page subclasses that use the public CurrentPage property to host an active child.

Those custom hosts will now silently stop propagating system back requests unless they know to register their own class handler. Is that intended? If not, we may need a shared non-recursive forwarding helper or another way to preserve the old base behavior for custom page hosts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is intended. Forwarding the event would most like result in another recursion issue. Requiring the dev to handle the event if their custom page class has unique back behavior would prevent that.

RoutedEvent.Register<Page, RoutedEventArgs>(
nameof(PageNavigationSystemBackButtonPressed),
RoutingStrategies.Bubble);
RoutingStrategies.Direct);

@jsuarezruiz jsuarezruiz Apr 27, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Changing the back event to Direct removes the old base Page forwarding path, but NavigationPage still only handles modals and stack popping. In the normal non-modal path, the active CurrentPage no longer gets OnSystemBackButtonPressed() / PageNavigationSystemBackButtonPressed before the navigation stack reacts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Having the CurrentPage get the event when it's not modal makes it have some modal behavior. Since if they handle the event, the page won't be popped. That would make it modal, won't it.

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065210-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@jsuarezruiz

Copy link
Copy Markdown
Member

Found one modal-ordering issue: NavigationPage now forwards system back to CurrentPage before checking _modalStack. When a modal is visible, CurrentPage is the covered page, so it can handle back before the visible modal sees it.

e07ec52

I tested moving modal handling before CurrentPage forwarding, and added tests for:

  • modal does not forward to covered current page
  • handled modal keeps modal stack
  • PageNavigationHost forwards to nested active child once
  • DrawerPage -> NavigationPage -> modal ordering

@avaloniaui-bot

Copy link
Copy Markdown

You can test this PR using the following package version. 12.1.999-cibuild0065321-alpha. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul
MrJul added this pull request to the merge queue May 13, 2026
Merged via the queue into master with commit 593b5a2 May 13, 2026
11 checks passed
@MrJul
MrJul deleted the page_navigation_back_handler branch May 13, 2026 14:36
MrJul pushed a commit to MrJul/Avalonia that referenced this pull request May 28, 2026
* ensure system back event is passed to child page once

* fix formatting in DrawerPage

* add tests

* add back default page handler for system back event, but only check OnSystemBackButtonPressed

* removed `OnSystemBackButtonPressed` checks in derived page classes

* nav page - send back event to current page before modal pages.

* Fix modal-first system back routing in NavigationPage

---------

Co-authored-by: Javier Suárez Ruiz <javiersuarezruiz@hotmail.com>
@MrJul MrJul added backported-12.0.x and removed backport-candidate-12.0.x Consider this PR for backporting to 12.0 branch labels May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants