Skip to content

WP 7.1: Tailwind generator throws in pattern preview iframes (observe on null document.body) and previews lose styling #3025

Description

@milicavs

Description

On WordPress 7.1, opening the Otter pattern selector (or any screen that renders block pattern previews, e.g. the editor inserter or Appearance > Patterns) floods the console with:

tailwind-generator-editor.js?ver=071355996f7c5acc3961:1 Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
    at tailwind-generator-editor.js:1:345438
    at HTMLDocument.eo (tailwind-generator-editor.js:1:345527)

One error per preview iframe. As a side effect, Atomic Wind pattern previews render without their Tailwind CSS (unstyled), because the observer that would regenerate CSS when the preview content arrives is never attached.

Root cause

bootstrapPreviewFrame() in src/atomic-wind/tailwind/generator.js runs inside every block preview iframe (the script is enqueued on enqueue_block_assets, so core copies it into preview iframes) and does:

const observer = new MutationObserver( schedule );
observer.observe( document.body, { ... } );

with no guard on document.body.

In WordPress 7.1, Gutenberg's Iframe component builds the iframe src doc with the scripts in <head> and either no <body> at all, or (React 18 path) a body that removes itself during parsing:

<body><script>document.currentScript.parentElement.remove()</script></body>

React then portals the real <body> into documentElement after mount (see packages/block-editor/src/components/iframe/index.jsx, getIframeSrc()). So when the generator's DOMContentLoaded handler (start()) fires inside a preview iframe, document.body is null and observe() throws.

Sequence in each preview frame:

  1. start() runs on DOMContentLoaded, isTopFrame() is false, window.name is empty (only the main canvas gets name="editor-canvas" from BlockCanvas), so bootstrapPreviewFrame() runs.
  2. The initial schedule()/apply() pass finds zero [class] elements (body does not exist yet) and returns.
  3. observer.observe( document.body, ... ) throws because document.body is null.
  4. The observer never attaches, so when React later inserts the body with the pattern markup, no CSS regeneration ever happens. The preview stays unstyled.

The main editor canvas is unaffected (it is styled by the top frame via iframe[name^="editor-canvas"]).

Suggested fix

Wait for the body before attaching, then nudge one regeneration so markup that arrived in the meantime is processed, e.g.:

const attach = () => {
	if ( ! document.body ) {
		( window.requestAnimationFrame || window.setTimeout )( attach );
		return;
	}
	observer.observe( document.body, { attributes: true, attributeFilter: [ 'class' ], childList: true, subtree: true } );
	schedule();
};
attach();

Observing document.documentElement instead would also work but risks a feedback loop, since apply() writes sheet.textContent into <head> on every pass and that mutation would re-trigger schedule().

Environment

  • WordPress 7.1 (always iframed post editor), PHP 8.3
  • Otter Blocks 3.2.3 (build hash 071355996f7c5acc3961 matches the shipped build/atomic-wind/tailwind-generator-editor.js)
  • Reproduced with hands-on access on a customer site; the unguarded call is still present on development
  • Internal ref: HelpScout conversation 3435911108

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions