Skip to content

Fix: Custom HTML block preview keeps expanding when iframe uses height:100vh#78677

Merged
ciampo merged 9 commits into
WordPress:trunkfrom
hbhalodia:fix/issue-78539
Jun 10, 2026
Merged

Fix: Custom HTML block preview keeps expanding when iframe uses height:100vh#78677
ciampo merged 9 commits into
WordPress:trunkfrom
hbhalodia:fix/issue-78539

Conversation

@hbhalodia

Copy link
Copy Markdown
Contributor

What?

Closes #78539

Why?

  • PR needs to fix the infinte expansion and scroll when custom HTML content is previewed with iframe that has 100vh height.

How?

  • PR fixes the regex pattern which does not match the correctly for the units.

Testing Instructions

  1. Add a Custom HTML block.
  2. Click Edit HTML.
  3. Add an iframe in the HTML tab. The iframe uses height:100vh:

<iframe src="https://www.photopea.com" style="width:100%; height:100vh; border:0;"></iframe>

  1. Click Update.
  2. After updating, the Custom HTML block preview becomes normal.

Testing Instructions for Keyboard

  • None

Screenshots or screencast

Before

Image

After

Screen.Recording.2026-05-26.at.3.46.56.PM.mov

Use of AI Tools

  • GitHub Copilot with Claude Opus 4.6
  • Used for debugging and fixing the issue. The fix was reviwed by me.

Detailed AI review

AI Summary Started

Root cause

The SandBox component (used by the Custom HTML block's preview) intentionally strips viewport-unit styles (vh, vw, etc.) from user-supplied HTML, because those units are relative to the iframe's own size and would otherwise create a measurement feedback loop.

That stripping is done by removeViewportStyles in packages/components/src/sandbox/index.tsx using this regex:

/^\\d+(vw|vh|svw|lvw|dvw|svh|lvh|dvh|vi|svi|lvi|dvi|vb|svb|lvb|dvb|vmin|svmin|lvmin|dvmin|vmax|svmax|lvmax|dvmax)$/

The pattern contains a double backslash (\\d). In a regex literal \\d means "a literal backslash followed by the letter d" — not "a digit". So the regex never matches any real CSS value, including 100vh, and the strip step is effectively a no-op.

Why this causes unbounded expansion

When the user puts <iframe style="height:100vh"> inside the Custom HTML block:

  1. The inline 100vh is not stripped (because of the broken regex).
  2. Inside the sandbox iframe, that inner <iframe> resolves 100vh against the sandbox iframe's current height.
  3. The sandbox's MutationObserver measures document.body.getBoundingClientRect() and posts the new height to the parent via postMessage.
  4. The parent component sets the sandbox iframe's height attribute to that value.
  5. The sandbox iframe is now taller → 100vh resolves to a larger value → body grows → observer fires → parent grows the iframe again → infinite loop.

That's what produces the "preview keeps expanding, editor becomes heavy, very tall scrollbar" behavior described in the issue.

Fix

Correct the regex (single backslash) and also accept decimal values like 50.5vh:

- /^\\d+(vw|vh|svw|lvw|dvw|svh|lvh|dvh|vi|svi|lvi|dvi|vb|svb|lvb|dvb|vmin|svmin|lvmin|dvmin|vmax|svmax|lvmax|dvmax)$/
+ /^\d+(?:\.\d+)?(vw|vh|svw|lvw|dvw|svh|lvh|dvh|vi|svi|lvi|dvi|vb|svb|lvb|dvb|vmin|svmin|lvmin|dvmin|vmax|svmax|lvmax|dvmax)$/

With this in place, 100vh is correctly identified and stripped before the first measurement, the feedback loop is broken, and the preview settles at a stable height.

Test steps

  1. Add a Custom HTML block.
  2. Paste: <iframe src="https://www.photopea.com" style="width:100%; height:100vh; border:0;"></iframe>
  3. Click Preview.

Before: the block grows continuously, vertical scrollbar appears, editor becomes sluggish.
After: the preview renders at a stable, finite height with no runaway growth.

AI Summary Completed


Regex101 Test

Previous regex

Link - https://regex101.com/?regex=%5E%5C%5Cd%2B%28vw%7Cvh%7Csvw%7Clvw%7Cdvw%7Csvh%7Clvh%7Cdvh%7Cvi%7Csvi%7Clvi%7Cdvi%7Cvb%7Csvb%7Clvb%7Cdvb%7Cvmin%7Csvmin%7Clvmin%7Cdvmin%7Cvmax%7Csvmax%7Clvmax%7Cdvmax%29%24&testString=100vh%0A&flags=&flavor=pcre2&delimiter=%2F

New Regex

Link - https://regex101.com/?regex=%5E%5Cd%2B%28%3F%3A%5C.%5Cd%2B%29%3F%28vw%7Cvh%7Csvw%7Clvw%7Cdvw%7Csvh%7Clvh%7Cdvh%7Cvi%7Csvi%7Clvi%7Cdvi%7Cvb%7Csvb%7Clvb%7Cdvb%7Cvmin%7Csvmin%7Clvmin%7Cdvmin%7Cvmax%7Csvmax%7Clvmax%7Cdvmax%29%24%0A&testString=100vh%0A&flags=&flavor=pcre2&delimiter=%2F

@hbhalodia hbhalodia self-assigned this May 26, 2026
@hbhalodia hbhalodia requested review from a team and ajitbohra as code owners May 26, 2026 10:25
@hbhalodia hbhalodia added [Type] Bug An existing feature does not function as intended [Block] HTML Affects the the HTML Block labels May 26, 2026
@github-actions github-actions Bot added the [Package] Components /packages/components label May 26, 2026

@ciampo ciampo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this!

I think the explanation of the root cause and the fix are correct. Before merging, we need:

  • a CHANGELOG entry
  • ideally, some unit tests. We could extract the regex into a VIEWPORT_UNIT_VALUE_REGEX constant or (if that's too complicated because of the fact that observeAndResizeJS is inline in a script) test the <SandBox html="<div style='height:100vh'>x</div>" /> component directly (alongside other meaningful tests)

Comment thread packages/components/src/sandbox/index.tsx Outdated
Comment thread packages/components/src/sandbox/index.tsx Outdated
@github-actions

github-actions Bot commented May 28, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @KeniVinh.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Unlinked contributors: KeniVinh.

Co-authored-by: hbhalodia <[email protected]>
Co-authored-by: t-hamano <[email protected]>
Co-authored-by: ciampo <[email protected]>
Co-authored-by: tyxla <[email protected]>
Co-authored-by: aaronjorbin <[email protected]>
Co-authored-by: Mustafabharmal <[email protected]>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@hbhalodia

Copy link
Copy Markdown
Contributor Author

Hi @ciampo, Thanks for the review. I have added the changelog entry. For tests, I tried as you shared but not sure what to test it exactly. I am bit confused there, hence tests are not added in the PR.

@ciampo ciampo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what we could do in terms of adding unit tests
diff --git a/packages/components/src/sandbox/index.tsx b/packages/components/src/sandbox/index.tsx
--- a/packages/components/src/sandbox/index.tsx
+++ b/packages/components/src/sandbox/index.tsx
@@ -16,6 +16,24 @@ import type { SandBoxProps } from './types';

 type SandBoxContentProps = Omit< SandBoxProps, 'allowSameOrigin' >;

+/**
+ * Matches CSS viewport-relative length values such as `100vh`, `50.5vw`,
+ * and `.5dvh`. Used to strip viewport units from user-supplied HTML inside
+ * the sandbox iframe, because those units are relative to the iframe's
+ * own size and would create a measurement feedback loop with the
+ * resize observer.
+ *
+ * Exported for tests. NOTE: an identical regex literal is duplicated
+ * inside `observeAndResizeJS` below because that function is serialized
+ * via `.toString()` and embedded into the iframe's `srcdoc` — it has no
+ * access to this module's scope at runtime. If you change one, change
+ * the other; the "is embedded in the sandbox iframe srcdoc" test
+ * guards against drift.
+ */
+export const VIEWPORT_UNIT_VALUE_REGEX =
+	/^\d*\.?\d+(?:vw|vh|svw|lvw|dvw|svh|lvh|dvh|vi|svi|lvi|dvi|vb|svb|lvb|dvb|vmin|svmin|lvmin|dvmin|vmax|svmax|lvmax|dvmax)$/;
+
 const observeAndResizeJS = function () {
 	const { MutationObserver } = window;

diff --git a/packages/components/src/sandbox/test/index.tsx b/packages/components/src/sandbox/test/index.tsx
--- a/packages/components/src/sandbox/test/index.tsx
+++ b/packages/components/src/sandbox/test/index.tsx
@@ -11,7 +11,7 @@ import { useState } from '@wordpress/element';
 /**
  * Internal dependencies
  */
-import SandBox from '..';
+import SandBox, { VIEWPORT_UNIT_VALUE_REGEX } from '..';

 describe( 'SandBox', () => {
 	const TestWrapper = () => {
@@ -108,4 +108,46 @@ describe( 'SandBox', () => {
 			expect.stringContaining( 'https://another.super.embed' )
 		);
 	} );
+
+	describe( 'VIEWPORT_UNIT_VALUE_REGEX', () => {
+		it.each( [
+			'100vh',
+			'50vw',
+			'0vh',
+			'50.5vh',
+			'.5vh',
+			'100dvh',
+			'50svw',
+			'1lvi',
+			'100vmin',
+			'100vmax',
+		] )( 'matches viewport unit value %s', ( value ) => {
+			expect( VIEWPORT_UNIT_VALUE_REGEX.test( value ) ).toBe( true );
+		} );
+
+		it.each( [
+			'100px',
+			'50%',
+			'100',
+			'vh',
+			'.vh',
+			'calc(100vh - 10px)',
+			'100 vh',
+			'',
+		] )( 'does not match %s', ( value ) => {
+			expect( VIEWPORT_UNIT_VALUE_REGEX.test( value ) ).toBe( false );
+		} );
+
+		it( 'is embedded in the sandbox iframe srcdoc', () => {
+			// Guards against drift between the exported constant and
+			// the copy inlined into `observeAndResizeJS`, which is
+			// serialized via `.toString()` into the iframe srcdoc and
+			// cannot reference module-scope values at runtime.
+			render( <SandBox html="<p>x</p>" title="Regex Sync Test" /> );
+			const iframe =
+				screen.getByTitle< HTMLIFrameElement >( 'Regex Sync Test' );
+			const srcDoc = iframe.getAttribute( 'srcdoc' ) ?? '';
+
+			expect( srcDoc ).toContain( VIEWPORT_UNIT_VALUE_REGEX.source );
+		} );
+	} );
 } );

The duplication is not ideal, but it's the best way we can test the regex in isolation

Comment thread packages/components/CHANGELOG.md Outdated
@hbhalodia hbhalodia requested review from ciampo and tyxla May 28, 2026 12:25

@ciampo ciampo left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

We'll be able to merge once feedback on the CHANGELOG is addressed

Comment thread packages/components/CHANGELOG.md Outdated
@t-hamano t-hamano added the Backport to WP Minor Release Pull request that needs to be backported to a WordPress minor release label May 28, 2026
@github-project-automation github-project-automation Bot moved this to 🔎 Needs Review in WordPress 7.0 Editor Tasks May 28, 2026
@t-hamano t-hamano removed the Backport to WP Minor Release Pull request that needs to be backported to a WordPress minor release label May 29, 2026
@t-hamano

Copy link
Copy Markdown
Contributor

This issue did not originate with WordPress 7.0, so it may not be necessary to backport it to the 7.0 minor release.

@ciampo

ciampo commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@hbhalodia can you rebase this PR on top of latest trunk and make sure that the CHANGELOG entry is under the latest Unreleased section?

We'll be able to merge after that

@hbhalodia

Copy link
Copy Markdown
Contributor Author

Hi @ciampo, This is now done. PR is now updated with the latest trunk and changelog entry is moved to unreleased section.

@ciampo ciampo merged commit 2cbccc1 into WordPress:trunk Jun 10, 2026
43 checks passed
@github-actions github-actions Bot added this to the Gutenberg 23.5 milestone Jun 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Block] HTML Affects the the HTML Block [Package] Components /packages/components [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom HTML block preview keeps expanding when iframe uses height:100vh

4 participants