UNPKG

@ionic/core

Version:
143 lines (142 loc) • 5.02 kB
/*! * (C) Ionic http://ionicframework.com - MIT License */ import { win } from "./browser/index"; import { printIonError } from "./logging/index"; import { config } from "../global/config"; /** * CloseWatcher is a newer API that lets * use detect the hardware back button event * in a web browser: https://caniuse.com/?search=closewatcher * However, not every browser supports it yet. * * This needs to be a function so that we can * check the config once it has been set. * Otherwise, this code would be evaluated the * moment this file is evaluated which could be * before the config is set. */ export const shouldUseCloseWatcher = () => config.get('experimentalCloseWatcher', false) && win !== undefined && 'CloseWatcher' in win; /** * When hardwareBackButton: false in config, * we need to make sure we also block the default * webview behavior. If we don't then it will be * possible for users to navigate backward while * an overlay is still open. Additionally, it will * give the appearance that the hardwareBackButton * config is not working as the page transition * will still happen. */ export const blockHardwareBackButton = () => { document.addEventListener('backbutton', () => { }); }; const closeWatcherConditions = []; let syncCloseWatcher; /** * Registers a check for whether the back button * has something to close. The CloseWatcher is only * active while one of these checks passes. */ export const addCloseWatcherCondition = (condition) => { closeWatcherConditions.push(condition); }; /** * Call this whenever something the back button * can close opens or closes. */ export const updateCloseWatcher = () => { syncCloseWatcher?.(); }; export const startHardwareBackButton = () => { const doc = document; let busy = false; const backButtonCallback = () => { if (busy) { return; } let index = 0; let handlers = []; const ev = new CustomEvent('ionBackButton', { bubbles: false, detail: { register(priority, handler) { handlers.push({ priority, handler, id: index++ }); }, }, }); doc.dispatchEvent(ev); const executeAction = async (handlerRegister) => { try { if (handlerRegister?.handler) { const result = handlerRegister.handler(processHandlers); if (result != null) { await result; } } } catch (e) { printIonError('[ion-app] - Exception in startHardwareBackButton:', e); } }; const processHandlers = () => { if (handlers.length > 0) { let selectedHandler = { 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(); }; /** * Android WebView never sends the hardware back * button to a CloseWatcher, so hybrid apps still * need this. Browsers never fire backbutton. */ doc.addEventListener('backbutton', backButtonCallback); if (shouldUseCloseWatcher()) { let watcher; /** * An active CloseWatcher stops the back button from * navigating, so only keep one while there's something to close. */ syncCloseWatcher = () => { const canClose = closeWatcherConditions.some((condition) => condition()); if (!canClose) { watcher?.destroy(); watcher = undefined; return; } if (watcher !== undefined) { return; } watcher = new win.CloseWatcher(); /** * Once a close request happens * the watcher gets destroyed. * As a result, we need to re-configure * the watcher so we can respond to other * close requests, including after one that * closed nothing, like a modal whose canDismiss * returned false. */ watcher.onclose = () => { watcher = undefined; backButtonCallback(); syncCloseWatcher?.(); }; }; syncCloseWatcher(); } }; 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