From def96ab3265c82f38dee106cb890b27474f4cdb5 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Mon, 8 Dec 2025 14:00:12 -0800 Subject: [PATCH 1/3] fix(modal): prevent browser hang when using ModalController in Angular --- core/src/components/modal/modal.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index b1a494430d0..f75471f1a78 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -1183,6 +1183,20 @@ export class Modal implements ComponentInterface, OverlayInterface { return; } + /** + * Don't observe for controller-based modals or when the parent is the + * app root (document.body or ion-app). These parents won't be removed, + * and observing document.body with subtree: true causes performance + * issues with frameworks like Angular during change detection. + */ + if ( + this.hasController || + this.cachedOriginalParent === document.body || + this.cachedOriginalParent.tagName === 'ION-APP' + ) { + return; + } + this.parentRemovalObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.removedNodes.length > 0) { From bda1911a5365e2a3e2dc163133b9cfd67a7d4751 Mon Sep 17 00:00:00 2001 From: ShaneK Date: Tue, 9 Dec 2025 08:54:15 -0800 Subject: [PATCH 2/3] test(modal): verify parentRemovalObserver is not set for controller modals --- .../components/modal/test/basic/modal.e2e.ts | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/core/src/components/modal/test/basic/modal.e2e.ts b/core/src/components/modal/test/basic/modal.e2e.ts index 325c4b3fbb5..919018b4631 100644 --- a/core/src/components/modal/test/basic/modal.e2e.ts +++ b/core/src/components/modal/test/basic/modal.e2e.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; -import { configs, test, Viewports } from '@utils/test/playwright'; import type { E2EPage } from '@utils/test/playwright'; +import { configs, test, Viewports } from '@utils/test/playwright'; configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('modal: focus trapping'), () => { @@ -104,6 +104,28 @@ configs().forEach(({ title, screenshot, config }) => { }); configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('modal: parent removal observer'), () => { + test('should not set up parentRemovalObserver for controller-created modals', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'https://github.com/ionic-team/ionic-framework/pull/30845', + }); + + await page.goto('/src/components/modal/test/basic', config); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#basic-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + const hasObserver = await modal.evaluate((el: any) => { + return el.parentRemovalObserver !== undefined; + }); + + expect(hasObserver).toBe(false); + }); + }); + test.describe(title('modal: backdrop'), () => { test.beforeEach(async ({ page }) => { await page.goto('/src/components/modal/test/basic', config); From c406ec8545e475be8485ff39bdbf16429ea8db6a Mon Sep 17 00:00:00 2001 From: Shane Date: Tue, 9 Dec 2025 09:03:59 -0800 Subject: [PATCH 3/3] Update core/src/components/modal/test/basic/modal.e2e.ts Co-authored-by: Maria Hutt --- core/src/components/modal/test/basic/modal.e2e.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/modal/test/basic/modal.e2e.ts b/core/src/components/modal/test/basic/modal.e2e.ts index 919018b4631..1c9ac92743c 100644 --- a/core/src/components/modal/test/basic/modal.e2e.ts +++ b/core/src/components/modal/test/basic/modal.e2e.ts @@ -108,7 +108,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => test('should not set up parentRemovalObserver for controller-created modals', async ({ page }, testInfo) => { testInfo.annotations.push({ type: 'issue', - description: 'https://github.com/ionic-team/ionic-framework/pull/30845', + description: 'FW-6766', }); await page.goto('/src/components/modal/test/basic', config);