diff --git a/packages/block-editor/src/components/iframe/index.js b/packages/block-editor/src/components/iframe/index.js index 719ecc2f68556f..d336188c29d488 100644 --- a/packages/block-editor/src/components/iframe/index.js +++ b/packages/block-editor/src/components/iframe/index.js @@ -6,13 +6,7 @@ import clsx from 'clsx'; /** * WordPress dependencies */ -import { - useState, - createPortal, - forwardRef, - useMemo, - useEffect, -} from '@wordpress/element'; +import { useState, createPortal, forwardRef } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; import { useMergeRefs, @@ -102,6 +96,54 @@ function useBubbleEvents( iframeDocument ) { } ); } +const iframeSrcCache = new WeakMap(); +const iframeSrcCleanup = globalThis.FinalizationRegistry + ? new globalThis.FinalizationRegistry( ( url ) => + URL.revokeObjectURL( url ) + ) + : undefined; + +function getIframeSrc( resolvedAssets ) { + let src = iframeSrcCache.get( resolvedAssets ); + if ( src ) { + return src; + } + + // Correct doctype is required to enable rendering in standards mode. + // Also preload the styles to avoid a flash of unstyled content. + const html = ` + + + + + + + ${ resolvedAssets.styles ?? '' } + ${ resolvedAssets.scripts ?? '' } + + + + +`; + + src = URL.createObjectURL( new Blob( [ html ], { type: 'text/html' } ) ); + iframeSrcCache.set( resolvedAssets, src ); + iframeSrcCleanup?.register( resolvedAssets, src ); + return src; +} + function Iframe( { contentRef, children, @@ -114,14 +156,12 @@ function Iframe( { ...props } ) { const { resolvedAssets, isPreviewMode } = useSelect( ( select ) => { - const { getSettings } = select( blockEditorStore ); - const settings = getSettings(); + const settings = select( blockEditorStore ).getSettings(); return { resolvedAssets: settings.__unstableResolvedAssets, isPreviewMode: settings.isPreviewMode, }; }, [] ); - const { styles = '', scripts = '' } = resolvedAssets; /** @type {[Document, React.Dispatch]} */ const [ iframeDocument, setIframeDocument ] = useState(); const [ bodyClasses, setBodyClasses ] = useState( [] ); @@ -251,44 +291,7 @@ function Iframe( { disabledRef, ] ); - // Correct doctype is required to enable rendering in standards - // mode. Also preload the styles to avoid a flash of unstyled - // content. - const html = ` - - - - - - - ${ styles } - ${ scripts } - - - - -`; - - const [ src, cleanup ] = useMemo( () => { - const _src = URL.createObjectURL( - new window.Blob( [ html ], { type: 'text/html' } ) - ); - return [ _src, () => URL.revokeObjectURL( _src ) ]; - }, [ html ] ); - - useEffect( () => cleanup, [ cleanup ] ); + const src = getIframeSrc( resolvedAssets ); // Make sure to not render the before and after focusable div elements in view // mode. They're only needed to capture focus in edit mode. @@ -307,9 +310,6 @@ function Iframe( { } } ref={ useMergeRefs( [ ref, setRef ] ) } tabIndex={ tabIndex } - // Correct doctype is required to enable rendering in standards - // mode. Also preload the styles to avoid a flash of unstyled - // content. src={ src } title={ title } onKeyDown={ ( event ) => {