Overview
Note: this handles style usage, Tailwind CSS classes were already fixed with #3534490: Cannot use `h-screen` from Tailwind with XB
The vh-unit detection algorithm in useSyncIframeHeightToContent (web/modules/contrib/canvas/ui/src/hooks/useSyncIframeHeightToContent.ts) is designed to detect elements whose height scales with the iframe viewport and apply a max-height cap to prevent an infinite height-growth feedback loop. The algorithm sets iframe.style.height to three different multiples (1×, 3×, 8×) of the base height and reads element.clientHeight on elements inside the iframe after each change, expecting heights to scale proportionally.
Setting iframe.style.height in the parent frame marks the parent frame's layout dirty but does not force the iframe's internal layout to recalculate synchronously. Reading element.clientHeight on a cross-frame element returns the iframe's last-committed internal layout — the stale pre-change value — because the browser has not yet propagated the new viewport dimensions into the iframe's rendering context. All three measurements return the same clientHeight, producing ratios of [H, H/3, H/8]. Since they are not all equal, no element is ever flagged as vh-based, no max-height cap is applied, and any component using height: Xvh or min-height: Xvh triggers the infinite loop.
Proposed resolution
Add void iframe.offsetHeight after setting iframe.style.height inside the multipliers loop in useSyncIframeHeightToContent.ts. Reading offsetHeight on the iframe element in the parent frame forces a synchronous parent-frame layout flush, committing the new iframe dimensions before the inner elements.forEach reads element.clientHeight. With the parent layout committed, the iframe's viewport height is updated and vh-unit elements reflow correctly at each multiplier step.
Current code (lines 74–84):
multipliers.forEach((multi) => { iframe.style.height = height * multi + 'px'; iframe.style.overflow = 'visible'; elements.forEach((element) => { const ratios: number[] = heightRatios.get(element) || []; if (element.clientHeight > 10) { ratios.push(Math.floor(element.clientHeight / multi)); heightRatios.set(element, ratios); } }); });
Proposed change:
multipliers.forEach((multi) => { iframe.style.height = height * multi + 'px'; iframe.style.overflow = 'visible'; // Force a synchronous parent-frame layout flush so the iframe's viewport // height propagates before reading cross-frame element.clientHeight. void iframe.offsetHeight; elements.forEach((element) => { const ratios: number[] = heightRatios.get(element) || []; if (element.clientHeight > 10) { ratios.push(Math.floor(element.clientHeight / multi)); heightRatios.set(element, ratios); } }); });
With this fix, clientHeight on elements using height: 100vh or min-height: 100vh will correctly read H, 3H, and 8H at the three multiplier steps, the ratios will all be equal, and those elements will receive a max-height cap that breaks the feedback loop.
User interface changes
None. This is an internal layout measurement fix. Components using vh-based heights that previously caused the editor preview to grow to millions of pixels will now render at a stable, capped height in the editor.
Issue fork canvas-3587909
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #5
lauriiiComment #7
mglaman