From fea614a3ac1f38cfa00589d66553603215f4df2f Mon Sep 17 00:00:00 2001
From: Kai Hao <kevin830726@gmail.com>
Date: Mon, 5 Jul 2021 19:51:53 +0800
Subject: [PATCH] Fix legacy widget height overflow (#33191)

---
 .../src/blocks/legacy-widget/edit/preview.js  | 82 +++++++++++++------
 1 file changed, 55 insertions(+), 27 deletions(-)

diff --git a/packages/widgets/src/blocks/legacy-widget/edit/preview.js b/packages/widgets/src/blocks/legacy-widget/edit/preview.js
index 39e4d65a51cb45..66109d62558ae9 100644
--- a/packages/widgets/src/blocks/legacy-widget/edit/preview.js
+++ b/packages/widgets/src/blocks/legacy-widget/edit/preview.js
@@ -13,36 +13,54 @@ import { Placeholder, Spinner, Disabled } from '@wordpress/components';
 import { __ } from '@wordpress/i18n';
 
 export default function Preview( { idBase, instance, isVisible } ) {
-	const [ iframeHeight, setIframeHeight ] = useState();
+	const [ isLoaded, setIsLoaded ] = useState( false );
 
 	// Resize the iframe on either the load event, or when the iframe becomes visible.
-	const ref = useRefEffect( ( iframe ) => {
-		function onChange() {
-			const boundingRect = iframe?.contentDocument?.body?.getBoundingClientRect();
-			if ( boundingRect ) {
-				// Include `top` in the height calculation to avoid the bottom
-				// of widget previews being cut-off. Most widgets have a
-				// heading at the top that has top margin, and the `height`
-				// alone doesn't take that margin into account.
-				setIframeHeight( boundingRect.top + boundingRect.height );
-			}
-		}
+	const ref = useRefEffect(
+		( iframe ) => {
+			// Only set height if the iframe is loaded,
+			// or it will grow to an unexpected large height in Safari if it's hidden initially.
+			if ( isLoaded ) {
+				// If the preview frame has another origin then this won't work.
+				// One possible solution is to add custom script to call `postMessage` in the preview frame.
+				// Or, better yet, we migrate away from iframe.
+				function setHeight() {
+					// Pick the maximum of these two values to account for margin collapsing.
+					const height = Math.max(
+						iframe.contentDocument.documentElement.offsetHeight,
+						iframe.contentDocument.body.offsetHeight
+					);
+					iframe.style.height = `${ height }px`;
+				}
 
-		const { IntersectionObserver } = iframe.ownerDocument.defaultView;
+				const {
+					IntersectionObserver,
+				} = iframe.ownerDocument.defaultView;
 
-		// Observe for intersections that might cause a change in the height of
-		// the iframe, e.g. a Widget Area becoming expanded.
-		const intersectionObserver = new IntersectionObserver( onChange, {
-			threshold: 1,
-		} );
-		intersectionObserver.observe( iframe );
+				// Observe for intersections that might cause a change in the height of
+				// the iframe, e.g. a Widget Area becoming expanded.
+				const intersectionObserver = new IntersectionObserver(
+					( [ entry ] ) => {
+						if ( entry.isIntersecting ) {
+							setHeight();
+						}
+					},
+					{
+						threshold: 1,
+					}
+				);
+				intersectionObserver.observe( iframe );
 
-		iframe.addEventListener( 'load', onChange );
+				iframe.addEventListener( 'load', setHeight );
 
-		return () => {
-			iframe.removeEventListener( 'load', onChange );
-		};
-	}, [] );
+				return () => {
+					intersectionObserver.disconnect();
+					iframe.removeEventListener( 'load', setHeight );
+				};
+			}
+		},
+		[ isLoaded ]
+	);
 
 	return (
 		<>
@@ -53,7 +71,7 @@ export default function Preview( { idBase, instance, isVisible } ) {
 			move the iframe off-screen instead of hiding it because web browsers
 			will not trigger onLoad if the iframe is hidden.
 			*/ }
-			{ isVisible && iframeHeight === null && (
+			{ isVisible && ! isLoaded && (
 				<Placeholder>
 					<Spinner />
 				</Placeholder>
@@ -62,7 +80,7 @@ export default function Preview( { idBase, instance, isVisible } ) {
 				className={ classnames(
 					'wp-block-legacy-widget__edit-preview',
 					{
-						'is-offscreen': ! isVisible || iframeHeight === null,
+						'is-offscreen': ! isVisible || ! isLoaded,
 					}
 				) }
 			>
@@ -84,7 +102,17 @@ export default function Preview( { idBase, instance, isVisible } ) {
 								instance,
 							},
 						} ) }
-						height={ iframeHeight || 100 }
+						onLoad={ ( event ) => {
+							// To hide the scrollbars of the preview frame for some edge cases,
+							// such as negative margins in the Gallery Legacy Widget.
+							// It can't be scrolled anyway.
+							// TODO: Ideally, this should be fixed in core.
+							event.target.contentDocument.body.style.overflow =
+								'hidden';
+
+							setIsLoaded( true );
+						} }
+						height={ 100 }
 					/>
 				</Disabled>
 			</div>