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:
start() runs on DOMContentLoaded, isTopFrame() is false, window.name is empty (only the main canvas gets name="editor-canvas" from BlockCanvas), so bootstrapPreviewFrame() runs.
- The initial
schedule()/apply() pass finds zero [class] elements (body does not exist yet) and returns.
observer.observe( document.body, ... ) throws because document.body is null.
- 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
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:
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()insrc/atomic-wind/tailwind/generator.jsruns inside every block preview iframe (the script is enqueued onenqueue_block_assets, so core copies it into preview iframes) and does:with no guard on
document.body.In WordPress 7.1, Gutenberg's
Iframecomponent 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:React then portals the real
<body>intodocumentElementafter mount (seepackages/block-editor/src/components/iframe/index.jsx,getIframeSrc()). So when the generator'sDOMContentLoadedhandler (start()) fires inside a preview iframe,document.bodyisnullandobserve()throws.Sequence in each preview frame:
start()runs onDOMContentLoaded,isTopFrame()is false,window.nameis empty (only the main canvas getsname="editor-canvas"fromBlockCanvas), sobootstrapPreviewFrame()runs.schedule()/apply()pass finds zero[class]elements (body does not exist yet) and returns.observer.observe( document.body, ... )throws becausedocument.bodyisnull.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.:
Observing
document.documentElementinstead would also work but risks a feedback loop, sinceapply()writessheet.textContentinto<head>on every pass and that mutation would re-triggerschedule().Environment
071355996f7c5acc3961matches the shippedbuild/atomic-wind/tailwind-generator-editor.js)development