diff --git a/core/src/utils/hardware-back-button.ts b/core/src/utils/hardware-back-button.ts index 2c79e3cfaa7..25b9423bf82 100644 --- a/core/src/utils/hardware-back-button.ts +++ b/core/src/utils/hardware-back-button.ts @@ -1,5 +1,5 @@ -import { win } from '@utils/browser'; import type { CloseWatcher } from '@utils/browser'; +import { win } from '@utils/browser'; import { printIonError } from '@utils/logging'; import { config } from '../global/config'; @@ -69,6 +69,21 @@ export const startHardwareBackButton = () => { }); doc.dispatchEvent(ev); + /** + * If no handlers have been registered, fall back to the default + * behavior of navigating back in history. This ensures the hardware + * back button works even when no router or custom handler is present. + */ + if (handlers.length === 0) { + handlers.push({ + priority: FALLBACK_BACK_BUTTON_PRIORITY, + handler: () => { + win?.history.back(); + }, + id: index++, + }); + } + const executeAction = async (handlerRegister: HandlerRegister | undefined) => { try { if (handlerRegister?.handler) { @@ -138,3 +153,4 @@ export const startHardwareBackButton = () => { export const OVERLAY_BACK_BUTTON_PRIORITY = 100; export const MENU_BACK_BUTTON_PRIORITY = 99; // 1 less than overlay priority since menu is displayed behind overlays +const FALLBACK_BACK_BUTTON_PRIORITY = -1; // Fallback when no other handlers are registered diff --git a/core/src/utils/test/hardware-back-button.spec.ts b/core/src/utils/test/hardware-back-button.spec.ts index 87f27d407cc..bd0885a068d 100644 --- a/core/src/utils/test/hardware-back-button.spec.ts +++ b/core/src/utils/test/hardware-back-button.spec.ts @@ -54,6 +54,47 @@ describe('Hardware Back Button', () => { dispatchBackButtonEvent(); expect(cbSpyTwo).toHaveBeenCalled(); }); + + it('should fall back to history.back() when no handlers are registered', () => { + const historyBackSpy = jest.fn(); + const originalBack = win?.history?.back; + if (win?.history) { + win.history.back = historyBackSpy; + } + + // Don't register any ionBackButton handlers + dispatchBackButtonEvent(); + + expect(historyBackSpy).toHaveBeenCalled(); + + // Restore original + if (win?.history && originalBack) { + win.history.back = originalBack; + } + }); + + it('should not call history.back() when a handler is registered', () => { + const historyBackSpy = jest.fn(); + const originalBack = win?.history?.back; + if (win?.history) { + win.history.back = historyBackSpy; + } + + const cbSpy = jest.fn(); + document.addEventListener('ionBackButton', (ev) => { + (ev as BackButtonEvent).detail.register(0, cbSpy); + }); + + dispatchBackButtonEvent(); + + expect(cbSpy).toHaveBeenCalled(); + expect(historyBackSpy).not.toHaveBeenCalled(); + + // Restore original + if (win?.history && originalBack) { + win.history.back = originalBack; + } + }); }); describe('Experimental Close Watcher', () => {