Skip to content

Commit 1b21e07

Browse files
BenOsodracShaneK
andauthored
fix(core): add fallback handler for hardware back button when no router is present (#30878)
Issue number: internal --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the new behavior? <!-- Please describe the behavior or changes that are being added by this PR. --> This pull request enhances the hardware back button functionality to ensure a consistent user experience, especially when no custom handlers are registered. The main improvement is the addition of a fallback handler that triggers the default browser back navigation when no other handlers are present. **Hardware Back Button Improvements:** * Added a fallback handler in `startHardwareBackButton` that navigates back in browser history (`win?.history.back()`) if no custom handlers are registered, ensuring the hardware back button always performs a meaningful action. * Introduced a constant `FALLBACK_BACK_BUTTON_PRIORITY` with a value of `-1` to manage the priority of the fallback handler. **Code Consistency:** * Moved the import of `win` from `@utils/browser` to group it with other imports for consistency. ## Does this introduce a breaking change? - [ ] Yes - [x] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> --------- Co-authored-by: ShaneK <[email protected]>
1 parent 34bcf95 commit 1b21e07

File tree

2 files changed

+58
-1
lines changed

2 files changed

+58
-1
lines changed

core/src/utils/hardware-back-button.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { win } from '@utils/browser';
21
import type { CloseWatcher } from '@utils/browser';
2+
import { win } from '@utils/browser';
33
import { printIonError } from '@utils/logging';
44

55
import { config } from '../global/config';
@@ -69,6 +69,21 @@ export const startHardwareBackButton = () => {
6969
});
7070
doc.dispatchEvent(ev);
7171

72+
/**
73+
* If no handlers have been registered, fall back to the default
74+
* behavior of navigating back in history. This ensures the hardware
75+
* back button works even when no router or custom handler is present.
76+
*/
77+
if (handlers.length === 0) {
78+
handlers.push({
79+
priority: FALLBACK_BACK_BUTTON_PRIORITY,
80+
handler: () => {
81+
win?.history.back();
82+
},
83+
id: index++,
84+
});
85+
}
86+
7287
const executeAction = async (handlerRegister: HandlerRegister | undefined) => {
7388
try {
7489
if (handlerRegister?.handler) {
@@ -138,3 +153,4 @@ export const startHardwareBackButton = () => {
138153

139154
export const OVERLAY_BACK_BUTTON_PRIORITY = 100;
140155
export const MENU_BACK_BUTTON_PRIORITY = 99; // 1 less than overlay priority since menu is displayed behind overlays
156+
const FALLBACK_BACK_BUTTON_PRIORITY = -1; // Fallback when no other handlers are registered

core/src/utils/test/hardware-back-button.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,47 @@ describe('Hardware Back Button', () => {
5454
dispatchBackButtonEvent();
5555
expect(cbSpyTwo).toHaveBeenCalled();
5656
});
57+
58+
it('should fall back to history.back() when no handlers are registered', () => {
59+
const historyBackSpy = jest.fn();
60+
const originalBack = win?.history?.back;
61+
if (win?.history) {
62+
win.history.back = historyBackSpy;
63+
}
64+
65+
// Don't register any ionBackButton handlers
66+
dispatchBackButtonEvent();
67+
68+
expect(historyBackSpy).toHaveBeenCalled();
69+
70+
// Restore original
71+
if (win?.history && originalBack) {
72+
win.history.back = originalBack;
73+
}
74+
});
75+
76+
it('should not call history.back() when a handler is registered', () => {
77+
const historyBackSpy = jest.fn();
78+
const originalBack = win?.history?.back;
79+
if (win?.history) {
80+
win.history.back = historyBackSpy;
81+
}
82+
83+
const cbSpy = jest.fn();
84+
document.addEventListener('ionBackButton', (ev) => {
85+
(ev as BackButtonEvent).detail.register(0, cbSpy);
86+
});
87+
88+
dispatchBackButtonEvent();
89+
90+
expect(cbSpy).toHaveBeenCalled();
91+
expect(historyBackSpy).not.toHaveBeenCalled();
92+
93+
// Restore original
94+
if (win?.history && originalBack) {
95+
win.history.back = originalBack;
96+
}
97+
});
5798
});
5899

59100
describe('Experimental Close Watcher', () => {

0 commit comments

Comments
 (0)