Fix vh-unit detection in useSyncIframeHeightToContent to force cross-frame layout reflow
>>> [!note] Migrated issue
<!-- Drupal.org comment -->
<!-- Migrated from issue #3587909. -->
Reported by: [mglaman](https://www.drupal.org/user/2416470)
Related to !1031
>>>
<h3 id="overview">Overview</h3>
<p><strong>Note: this handles <code>style</code> usage, Tailwind CSS classes were already fixed with <span class="drupalorg-gitlab-issue-link project-issue-status-info project-issue-status-7"><a href="https://www.drupal.org/project/experience_builder/issues/3534490" title="Status: Closed (fixed)">#3534490: Cannot use `h-screen` from Tailwind with XB</a></span></strong></p>
<p>The vh-unit detection algorithm in <code>useSyncIframeHeightToContent</code> (<code>web/modules/contrib/canvas/ui/src/hooks/useSyncIframeHeightToContent.ts</code>) is designed to detect elements whose height scales with the iframe viewport and apply a <code>max-height</code> cap to prevent an infinite height-growth feedback loop. The algorithm sets <code>iframe.style.height</code> to three different multiples (1×, 3×, 8×) of the base height and reads <code>element.clientHeight</code> on elements inside the iframe after each change, expecting heights to scale proportionally.</p>
<p>Setting <code>iframe.style.height</code> in the parent frame marks the parent frame's layout dirty but does <strong>not</strong> force the iframe's internal layout to recalculate synchronously. Reading <code>element.clientHeight</code> 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 <code>clientHeight</code>, producing ratios of <code>[H, H/3, H/8]</code>. Since they are not all equal, no element is ever flagged as vh-based, no <code>max-height</code> cap is applied, and any component using <code>height: Xvh</code> or <code>min-height: Xvh</code> triggers the infinite loop.</p>
<h3 id="proposed-resolution">Proposed resolution</h3>
<p>Add <code>void iframe.offsetHeight</code> after setting <code>iframe.style.height</code> inside the multipliers loop in <code>useSyncIframeHeightToContent.ts</code>. Reading <code>offsetHeight</code> on the <code>iframe</code> element in the parent frame forces a synchronous parent-frame layout flush, committing the new iframe dimensions before the inner <code>elements.forEach</code> reads <code>element.clientHeight</code>. With the parent layout committed, the iframe's viewport height is updated and vh-unit elements reflow correctly at each multiplier step.</p>
<p>Current code (lines 74–84):</p>
<pre><pre>multipliers.forEach((multi) => {<br> iframe.style.height = height * multi + 'px';<br> iframe.style.overflow = 'visible';<br> elements.forEach((element) => {<br> const ratios: number[] = heightRatios.get(element) || [];<br> if (element.clientHeight > 10) {<br> ratios.push(Math.floor(element.clientHeight / multi));<br> heightRatios.set(element, ratios);<br> }<br> });<br>});</pre></pre><p>Proposed change:</p>
<pre><pre>multipliers.forEach((multi) => {<br> iframe.style.height = height * multi + 'px';<br> iframe.style.overflow = 'visible';<br> // Force a synchronous parent-frame layout flush so the iframe's viewport<br> // height propagates before reading cross-frame element.clientHeight.<br> void iframe.offsetHeight;<br> elements.forEach((element) => {<br> const ratios: number[] = heightRatios.get(element) || [];<br> if (element.clientHeight > 10) {<br> ratios.push(Math.floor(element.clientHeight / multi));<br> heightRatios.set(element, ratios);<br> }<br> });<br>});</pre></pre><p>With this fix, <code>clientHeight</code> on elements using <code>height: 100vh</code> or <code>min-height: 100vh</code> will correctly read <code>H</code>, <code>3H</code>, and <code>8H</code> at the three multiplier steps, the ratios will all be equal, and those elements will receive a <code>max-height</code> cap that breaks the feedback loop.</p>
<h3 id="ui-changes">User interface changes</h3>
<p>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.</p>
issue