diff --git a/angular/src/providers/nav-controller.ts b/angular/src/providers/nav-controller.ts index da2458cc76..a5d8786ea0 100644 --- a/angular/src/providers/nav-controller.ts +++ b/angular/src/providers/nav-controller.ts @@ -45,7 +45,10 @@ export class NavController { } // Subscribe to backButton events - platform.backButton.subscribeWithPriority(0, () => this.pop()); + platform.backButton.subscribeWithPriority(0, processNextHandler => { + this.pop(); + processNextHandler(); + }); } /** diff --git a/angular/src/providers/platform.ts b/angular/src/providers/platform.ts index 8071b3412c..282b85f196 100644 --- a/angular/src/providers/platform.ts +++ b/angular/src/providers/platform.ts @@ -4,7 +4,7 @@ import { BackButtonEventDetail, Platforms, getPlatforms, isPlatform } from '@ion import { Subject, Subscription } from 'rxjs'; export interface BackButtonEmitter extends Subject { - subscribeWithPriority(priority: number, callback: () => Promise | void): Subscription; + subscribeWithPriority(priority: number, callback: (processNextHandler: () => void) => Promise | void): Subscription; } @Injectable({ @@ -46,9 +46,9 @@ export class Platform { zone.run(() => { this.win = doc.defaultView; this.backButton.subscribeWithPriority = function(priority, callback) { - return this.subscribe(ev => ( - ev.register(priority, () => zone.run(callback)) - )); + return this.subscribe(ev => { + return ev.register(priority, processNextHandler => zone.run(() => callback(processNextHandler))); + }); }; proxyEvent(this.pause, doc, 'pause'); diff --git a/core/src/components/router/router.tsx b/core/src/components/router/router.tsx index 7a59d038f6..f458ac0cfb 100644 --- a/core/src/components/router/router.tsx +++ b/core/src/components/router/router.tsx @@ -79,7 +79,10 @@ export class Router implements ComponentInterface { @Listen('ionBackButton', { target: 'document' }) protected onBackButton(ev: BackButtonEvent) { - ev.detail.register(0, () => this.back()); + ev.detail.register(0, processNextHandler => { + this.back(); + processNextHandler(); + }); } /** diff --git a/core/src/interface.d.ts b/core/src/interface.d.ts index 804cdc7906..d422496903 100644 --- a/core/src/interface.d.ts +++ b/core/src/interface.d.ts @@ -53,7 +53,7 @@ export interface FrameworkDelegate { } export interface BackButtonEventDetail { - register(priority: number, handler: () => Promise | void): void; + register(priority: number, handler: (processNextHandler: () => void) => Promise | void): void; } export interface StyleEventDetail { diff --git a/core/src/utils/hardware-back-button.ts b/core/src/utils/hardware-back-button.ts index c73a7169fc..1186783708 100644 --- a/core/src/utils/hardware-back-button.ts +++ b/core/src/utils/hardware-back-button.ts @@ -1,10 +1,11 @@ import { BackButtonEvent } from '../interface'; -type Handler = () => Promise | void | null; +type Handler = (processNextHandler: () => void) => Promise | void | null; interface HandlerRegister { priority: number; handler: Handler; + id: number; } export const startHardwareBackButton = () => { @@ -16,44 +17,52 @@ export const startHardwareBackButton = () => { return; } - const handlers: HandlerRegister[] = []; + let index = 0; + let handlers: HandlerRegister[] = []; const ev: BackButtonEvent = new CustomEvent('ionBackButton', { bubbles: false, detail: { register(priority: number, handler: Handler) { - handlers.push({ priority, handler }); + handlers.push({ priority, handler, id: index++ }); } } }); doc.dispatchEvent(ev); - if (handlers.length > 0) { - let selectedPriority = Number.MIN_SAFE_INTEGER; - let selectedHandler: Handler | undefined; - handlers.forEach(({ priority, handler }) => { - if (priority >= selectedPriority) { - selectedPriority = priority; - selectedHandler = handler; + const executeAction = async (handlerRegister: HandlerRegister | undefined) => { + try { + if (handlerRegister && handlerRegister.handler) { + const result = handlerRegister.handler(processHandlers); + if (result != null) { + await result; + } } - }); - - busy = true; - executeAction(selectedHandler).then(() => busy = false); - } - }); -}; - -const executeAction = async (handler: Handler | undefined) => { - try { - if (handler) { - const result = handler(); - if (result != null) { - await result; + } catch (e) { + console.error(e); } - } - } catch (e) { - console.error(e); - } + }; + + const processHandlers = () => { + if (handlers.length > 0) { + let selectedHandler: HandlerRegister = { + priority: Number.MIN_SAFE_INTEGER, + handler: () => undefined, + id: -1 + }; + handlers.forEach(handler => { + if (handler.priority >= selectedHandler.priority) { + selectedHandler = handler; + } + }); + + busy = true; + handlers = handlers.filter(handler => handler.id !== selectedHandler.id); + executeAction(selectedHandler).then(() => busy = false); + } + }; + + processHandlers(); + }); }; export const OVERLAY_BACK_BUTTON_PRIORITY = 100; diff --git a/core/src/utils/test/hardware-back-button.spec.ts b/core/src/utils/test/hardware-back-button.spec.ts new file mode 100644 index 0000000000..7487fe187d --- /dev/null +++ b/core/src/utils/test/hardware-back-button.spec.ts @@ -0,0 +1,59 @@ +import { startHardwareBackButton } from '../hardware-back-button'; + +describe('Hardware Back Button', () => { + beforeEach(() => startHardwareBackButton()); + it('should call handler', () => { + const cbSpy = jest.fn(); + document.addEventListener('ionBackButton', (ev) => { + ev.detail.register(0, cbSpy); + }); + + dispatchBackButtonEvent(); + expect(cbSpy).toHaveBeenCalled(); + }); + + it('should call handlers in order of priority', () => { + const cbSpy = jest.fn(); + const cbSpyTwo = jest.fn(); + document.addEventListener('ionBackButton', (ev) => { + ev.detail.register(100, cbSpy); + ev.detail.register(99, cbSpyTwo); + }); + + dispatchBackButtonEvent(); + expect(cbSpy).toHaveBeenCalled(); + expect(cbSpyTwo).not.toHaveBeenCalled(); + }); + + it('should only call last handler to be added for handlers with same priority', () => { + const cbSpy = jest.fn(); + const cbSpyTwo = jest.fn(); + document.addEventListener('ionBackButton', (ev) => { + ev.detail.register(100, cbSpy); + ev.detail.register(100, cbSpyTwo); + }); + + dispatchBackButtonEvent(); + expect(cbSpy).not.toHaveBeenCalled(); + expect(cbSpyTwo).toHaveBeenCalled(); + }); + + it('should call multiple callbacks', () => { + const cbSpy = (processNextHandler) => { + processNextHandler(); + } + const cbSpyTwo = jest.fn(); + document.addEventListener('ionBackButton', (ev) => { + ev.detail.register(100, cbSpy); + ev.detail.register(99, cbSpyTwo); + }); + + dispatchBackButtonEvent(); + expect(cbSpyTwo).toHaveBeenCalled(); + }); +}); + +const dispatchBackButtonEvent = () => { + const ev = new Event('backbutton'); + document.dispatchEvent(ev); +} \ No newline at end of file diff --git a/packages/react-router/src/ReactRouter/NavManager.tsx b/packages/react-router/src/ReactRouter/NavManager.tsx index 98dcf602bc..497263f5d6 100644 --- a/packages/react-router/src/ReactRouter/NavManager.tsx +++ b/packages/react-router/src/ReactRouter/NavManager.tsx @@ -36,8 +36,9 @@ export class NavManager extends React.Component { - e.detail.register(0, () => { + e.detail.register(0, (processNextHandler: () => void) => { this.props.history.goBack(); + processNextHandler(); }); }); } diff --git a/vue/src/router.ts b/vue/src/router.ts index ba3fa90de5..d123fa4fce 100644 --- a/vue/src/router.ts +++ b/vue/src/router.ts @@ -37,7 +37,10 @@ export default class Router extends VueRouter { // Listen to Ionic's back button event document.addEventListener('ionBackButton', (e: Event) => { - (e as BackButtonEvent).detail.register(0, () => this.back()); + (e as BackButtonEvent).detail.register(0, processNextHandler => { + this.back(); + processNextHandler(); + }); }); }