From 261ab8c7cf7d6f7783f1f3bf86598233a4274b44 Mon Sep 17 00:00:00 2001 From: lebaudantoine Date: Thu, 6 Aug 2026 17:55:29 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20unmount=20PiP=20portal?= =?UTF-8?q?=20synchronously=20on=20pagehide?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the PiP window closes, the browser destroys its document right after `pagehide`. If the portal unmount is left to React's async scheduling, it commits against a dead document and `removeChild` throws "NotFoundError", crashing the app. Subscribe `PictureInPicturePortal` to the Valtio store with `sync: true`, and use `flushSync` in `usePictureInPicture` on teardown so React unmounts the portal while the PiP document is still alive. Fix 019f42cf-86a9-7ad2-8e64-81b004ddc5de --- .../src/features/pip/components/PictureInPicturePortal.tsx | 6 ++++-- src/frontend/src/features/pip/hooks/usePictureInPicture.ts | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/frontend/src/features/pip/components/PictureInPicturePortal.tsx b/src/frontend/src/features/pip/components/PictureInPicturePortal.tsx index 326e40a3..372656aa 100644 --- a/src/frontend/src/features/pip/components/PictureInPicturePortal.tsx +++ b/src/frontend/src/features/pip/components/PictureInPicturePortal.tsx @@ -7,7 +7,9 @@ import { useEffect, useMemo } from 'react' import { CrossDocumentOverlaysContext } from '@/primitives/CrossDocumentOverlaysContext' const InternalPortal = ({ children }: { children: React.ReactNode }) => { - const pipStoreSnap = useSnapshot(documentPictureInPictureStore) + const pipStoreSnap = useSnapshot(documentPictureInPictureStore, { + sync: true, + }) const container = useMemo(() => { return pipStoreSnap?.window?.document.getElementById('root') @@ -19,7 +21,7 @@ const InternalPortal = ({ children }: { children: React.ReactNode }) => { } }, []) - if (!container) return null + if (!container || !container.isConnected) return null return createPortal( /** diff --git a/src/frontend/src/features/pip/hooks/usePictureInPicture.ts b/src/frontend/src/features/pip/hooks/usePictureInPicture.ts index 68a31055..475a3f0b 100644 --- a/src/frontend/src/features/pip/hooks/usePictureInPicture.ts +++ b/src/frontend/src/features/pip/hooks/usePictureInPicture.ts @@ -1,5 +1,6 @@ import { ref, useSnapshot } from 'valtio' import { useCallback, useMemo } from 'react' +import { flushSync } from 'react-dom' import { documentPictureInPictureStore } from '@/stores/documentPictureInPicture' import { useTranslation } from 'react-i18next' @@ -73,7 +74,9 @@ export const usePictureInPicture = () => { const cleanUp = () => { if (documentPictureInPictureStore.window === pipWindow) { - documentPictureInPictureStore.window = null + flushSync(() => { + documentPictureInPictureStore.window = null + }) } } pipWindow.addEventListener('pagehide', () => cleanUp(), { once: true })