wp-block-editor-iframe-compatibility
Implement or audit WordPress block editor extensions for the always-iframed post editor in WordPress 7.1. Covers parent UI versus editor-canvas documents, ownerDocument/defaultView, @wordpress/compose useRefEffect, DOM events and observers, block and editor asset placement, block
Install
npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/wordpress/wp-block-editor-iframe-compatibility
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart
git clone https://github.com/Lonsdale201/wp-agent-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole lonsdale201/wp-agent-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
WordPress Block Editor Iframe Compatibility
In WordPress 7.1, the post editor's content canvas is always rendered in an iframe. This no longer depends on the active theme, the current content, or whether every inserted block declares API version 3. Editor-shell controls and block content therefore live in different documents.
Identify the target surface first
| Surface | Typical code | Asset/API choice |
|---|---|---|
| Editor shell/sidebar/toolbar | Plugin sidebar, notices, inspector panels | enqueue_block_editor_assets, WordPress components/data APIs |
| Canvas content | Block markup and content-facing styles | block metadata style/editorStyle, enqueue_block_assets where appropriate |
| Frontend | Saved/dynamic block output | block metadata style, render.php/callback, frontend enqueue |
Do not solve a canvas problem by globally injecting assets into the parent admin document, or a sidebar problem by loading code into every block content context.
Stop treating global document as the canvas
In an editor extension bundle, window and document normally refer to the parent editor shell. Derive the correct browsing context from the element you own:
import { useRefEffect } from '@wordpress/compose';
export function CanvasAwareControl() {
const ref = useRefEffect( ( element ) => {
const canvasDocument = element.ownerDocument;
const canvasWindow = canvasDocument.defaultView;
const handlePointerDown = ( event ) => {
// Handle only events for this canvas document.
};
canvasDocument.addEventListener( 'pointerdown', handlePointerDown );
canvasWindow?.addEventListener( 'resize', handlePointerDown );
return () => {
canvasDocument.removeEventListener( 'pointerdown', handlePointerDown );
canvasWindow?.removeEventListener( 'resize', handlePointerDown );
};
}, [] );
return <div ref={ ref } />;
}
Use the same rule for getSelection(), getComputedStyle(), ResizeObserver, MutationObserver, Range, element creation, and event constructors. Resolve constructors from element.ownerDocument.defaultView when realm identity matters.
Avoid locating iframe[name="editor-canvas"] and reaching through contentDocument. That couples code to editor markup and timing. A ref to the relevant rendered element is the durable boundary.
Prefer editor APIs over DOM scraping
Use @wordpress/data, block-editor hooks/components, and block attributes for editor state. DOM queries such as .block-editor-block-list__block, parent-document key listeners, and private class names are fragile even when pointed at the correct document.
When direct DOM integration is unavoidable:
- scope selectors to the owned element or its
ownerDocument; - attach and remove listeners through a ref lifecycle;
- tolerate remounts and document replacement;
- never store a canvas document/element globally across editor navigation;
- avoid mutation observers over the whole document when a component ref suffices.
Put styles in the correct document
For registered blocks, declare assets in block.json:
{
"apiVersion": 3,
"name": "myplugin/card",
"style": "file:./style-index.css",
"editorStyle": "file:./index.css"
}
Use style for content that must look correct in both saved output and editor canvas. Use editorStyle only for editor-specific canvas presentation. Use enqueue_block_editor_assets for editor-shell UI code/styles, not for frontend content CSS.
Do not append <style> to global document.head and expect it to style the canvas. If a runtime style truly belongs to the canvas, insert it through a component/ref tied to ownerDocument and clean it up, or generate a stable stylesheet through WordPress' block/style APIs.
Popovers, dialogs, and overlays
Prefer WordPress component primitives and provided slot/fill systems. Hand-built portals to document.body render in the parent shell and can misalign with canvas elements. If custom positioning is unavoidable, keep both the anchor measurements and overlay document explicit; do not mix coordinate systems from parent and iframe windows.
Keyboard/focus handling must also stay within the right document. Test Tab, Escape, focus return, scroll, zoom, and RTL in both post and site editors.
Account for the persistent admin toolbar
WordPress 7.1 shows the admin toolbar consistently in the Post and Site Editors. Avoid hardcoded viewport offsets and CSS that assumes the editor starts at top: 0. Use layout primitives and measured geometry. When conditionally hiding or altering admin-bar nodes, gate by get_current_screen()->is_block_editor() or the relevant screen ID instead of inferring from URL fragments.
Account for media isolation
The WP 7.1 client media pipeline can add Document-Isolation-Policy: isolate-and-credentialless on supported block-editor requests. External scripts/styles/media used in the editor need compatible CORS behavior, and strict CSPs may need worker support. Use wp-client-side-media-processing for the full isolation checklist.
Migration audit
Search editor code for:
document.querySelector
document.body
document.head
window.getSelection
window.getComputedStyle
window.addEventListener
new ResizeObserver
new MutationObserver
contentDocument
contentWindow
editor-canvas
createPortal
Each occurrence is not automatically wrong. Classify whether it targets the shell or canvas, then derive the intended document explicitly.
Test matrix
- post and page editors;
- custom post type with REST/block-editor support;
- classic and block themes;
- empty content and content containing legacy/API-v2 blocks;
- sidebar/toolbar UI and canvas interaction together;
- selection, keyboard, overlay positioning, scroll, zoom, RTL;
- editor navigation/remount without duplicate listeners;
- production CORS/CSP with media processing enabled.
Critical rules
- Treat editor shell and canvas as separate documents.
- Derive canvas DOM APIs from an owned element's
ownerDocumentanddefaultView. - Clean up every document/window observer and listener on ref teardown.
- Use block metadata and editor hooks for assets; do not spray styles into
document.head. - Prefer stable WordPress editor APIs over private DOM classes.
- Never use a hardcoded iframe selector as the integration contract.
Cross-references
- Use
wp-plugin-assets-loadingfor PHP enqueue contracts. - Use
wp-client-side-media-processingfor DIP, CORS, CSP, and WASM effects. - Use
wp-admin-postbox-sortablefor classic metabox/postbox behavior outside the canvas contract.
References
- Read
references/migration-patterns.mdfor before/after DOM patterns and asset placement. - Iframed editor changes dev note: https://make.wordpress.org/core/2026/08/03/iframed-editor-changes-in-wordpress-7-1/
- Persistent toolbar dev note: https://make.wordpress.org/core/2026/07/13/consistent-navigation-in-wordpress-7-1-with-persistent-toolbar/
- Core sources:
wp-includes/script-loader.php, block editor package builds, and block metadata asset registration.
Files (wp-agent-skills)
-
agents
-
openai.yaml 305 B
interface: display_name: "WP Block Editor Iframe Compatibility" short_description: "Make editor extensions safe for the always-iframed canvas" default_prompt: "Use $wp-block-editor-iframe-compatibility to audit or fix a WordPress block editor extension for the WordPress 7.1 always-iframed canvas."
-
-
references
-
migration-patterns.md 2.3 KB
# Always-iframed editor migration patterns ## DOM lookup ```js // Wrong when the target is block content: parent editor document. const target = document.querySelector( '.myplugin-card' ); // Better: scope from an element rendered in the target document. const target = element.ownerDocument.querySelector( '.myplugin-card' ); // Best where possible: hold a ref to the target itself and avoid a document query. ``` ## Selection and computed style ```js const ownerWindow = element.ownerDocument.defaultView; const selection = ownerWindow?.getSelection(); const computed = ownerWindow?.getComputedStyle( element ); ``` Do not combine a parent-window selection or viewport measurement with a canvas element rectangle. ## Observers and realm-sensitive constructors ```js const OwnerResizeObserver = element.ownerDocument.defaultView?.ResizeObserver; if ( OwnerResizeObserver ) { const observer = new OwnerResizeObserver( callback ); observer.observe( element ); // Disconnect during ref/effect cleanup. } ``` This avoids cross-realm assumptions and makes teardown explicit. ## Asset placement | Need | Preferred mechanism | |---|---| | Shared block content CSS | `block.json` `style` | | Canvas-only block CSS | `block.json` `editorStyle` | | Sidebar/toolbar JavaScript | `enqueue_block_editor_assets` | | General frontend and canvas block asset | `enqueue_block_assets`, with screen/feature gating as needed | | Dynamic per-instance visual value | block attributes/style engine or scoped inline style tied to owned canvas element | ## Common failures - Listener added to the parent `document`, so canvas keyboard/pointer events never arrive. - CSS appended to the parent head, so blocks appear unstyled in the iframe. - Portal rendered into the parent body while positioned with iframe-relative coordinates. - Cached `contentDocument` survives an editor remount and points at a detached document. - `window.getSelection()` reads the editor shell selection instead of the canvas selection. - fixed positioning assumes the admin toolbar is absent. ## Source/release checks Test against the built editor shipped by the target WordPress release. The public contract is the separate canvas document; iframe markup, private class names, and package-private exports are not extension APIs.
-
-
SKILL.md 8.1 KB
--- name: wp-block-editor-iframe-compatibility description: Implement or audit WordPress block editor extensions for the always-iframed post editor in WordPress 7.1. Covers parent UI versus editor-canvas documents, ownerDocument/defaultView, @wordpress/compose useRefEffect, DOM events and observers, block and editor asset placement, block metadata styles, portals/popovers, iframe-safe selections and measurements, persistent admin toolbar effects, classic-theme behavior, Document-Isolation-Policy interaction, and migration testing. Use when editor JavaScript queries document/window, injects styles, listens globally, measures blocks, renders overlays, manipulates selection, adds metabox/editor UI, or breaks after upgrading to WP 7.1. license: GPLv2-or-later metadata: wp-skills-author: "Soczó Kristóf" wp-skills-contact: "mailto:lonsdale201@hotmail.com" wp-skills-plugin: "wordpress" wp-skills-plugin-version-tested: "7.1" wp-skills-wp-version-tested: "7.1" wp-skills-php-min: "7.4" wp-skills-last-updated: "2026-08-19" --- # WordPress Block Editor Iframe Compatibility In WordPress 7.1, the post editor's content canvas is always rendered in an iframe. This no longer depends on the active theme, the current content, or whether every inserted block declares API version 3. Editor-shell controls and block content therefore live in different documents. ## Identify the target surface first | Surface | Typical code | Asset/API choice | |---|---|---| | Editor shell/sidebar/toolbar | Plugin sidebar, notices, inspector panels | `enqueue_block_editor_assets`, WordPress components/data APIs | | Canvas content | Block markup and content-facing styles | block metadata `style`/`editorStyle`, `enqueue_block_assets` where appropriate | | Frontend | Saved/dynamic block output | block metadata `style`, `render.php`/callback, frontend enqueue | Do not solve a canvas problem by globally injecting assets into the parent admin document, or a sidebar problem by loading code into every block content context. ## Stop treating global `document` as the canvas In an editor extension bundle, `window` and `document` normally refer to the parent editor shell. Derive the correct browsing context from the element you own: ```js import { useRefEffect } from '@wordpress/compose'; export function CanvasAwareControl() { const ref = useRefEffect( ( element ) => { const canvasDocument = element.ownerDocument; const canvasWindow = canvasDocument.defaultView; const handlePointerDown = ( event ) => { // Handle only events for this canvas document. }; canvasDocument.addEventListener( 'pointerdown', handlePointerDown ); canvasWindow?.addEventListener( 'resize', handlePointerDown ); return () => { canvasDocument.removeEventListener( 'pointerdown', handlePointerDown ); canvasWindow?.removeEventListener( 'resize', handlePointerDown ); }; }, [] ); return <div ref={ ref } />; } ``` Use the same rule for `getSelection()`, `getComputedStyle()`, `ResizeObserver`, `MutationObserver`, `Range`, element creation, and event constructors. Resolve constructors from `element.ownerDocument.defaultView` when realm identity matters. Avoid locating `iframe[name="editor-canvas"]` and reaching through `contentDocument`. That couples code to editor markup and timing. A ref to the relevant rendered element is the durable boundary. ## Prefer editor APIs over DOM scraping Use `@wordpress/data`, block-editor hooks/components, and block attributes for editor state. DOM queries such as `.block-editor-block-list__block`, parent-document key listeners, and private class names are fragile even when pointed at the correct document. When direct DOM integration is unavoidable: - scope selectors to the owned element or its `ownerDocument`; - attach and remove listeners through a ref lifecycle; - tolerate remounts and document replacement; - never store a canvas document/element globally across editor navigation; - avoid mutation observers over the whole document when a component ref suffices. ## Put styles in the correct document For registered blocks, declare assets in `block.json`: ```json { "apiVersion": 3, "name": "myplugin/card", "style": "file:./style-index.css", "editorStyle": "file:./index.css" } ``` Use `style` for content that must look correct in both saved output and editor canvas. Use `editorStyle` only for editor-specific canvas presentation. Use `enqueue_block_editor_assets` for editor-shell UI code/styles, not for frontend content CSS. Do not append `<style>` to global `document.head` and expect it to style the canvas. If a runtime style truly belongs to the canvas, insert it through a component/ref tied to `ownerDocument` and clean it up, or generate a stable stylesheet through WordPress' block/style APIs. ## Popovers, dialogs, and overlays Prefer WordPress component primitives and provided slot/fill systems. Hand-built portals to `document.body` render in the parent shell and can misalign with canvas elements. If custom positioning is unavoidable, keep both the anchor measurements and overlay document explicit; do not mix coordinate systems from parent and iframe windows. Keyboard/focus handling must also stay within the right document. Test Tab, Escape, focus return, scroll, zoom, and RTL in both post and site editors. ## Account for the persistent admin toolbar WordPress 7.1 shows the admin toolbar consistently in the Post and Site Editors. Avoid hardcoded viewport offsets and CSS that assumes the editor starts at `top: 0`. Use layout primitives and measured geometry. When conditionally hiding or altering admin-bar nodes, gate by `get_current_screen()->is_block_editor()` or the relevant screen ID instead of inferring from URL fragments. ## Account for media isolation The WP 7.1 client media pipeline can add `Document-Isolation-Policy: isolate-and-credentialless` on supported block-editor requests. External scripts/styles/media used in the editor need compatible CORS behavior, and strict CSPs may need worker support. Use `wp-client-side-media-processing` for the full isolation checklist. ## Migration audit Search editor code for: ```text document.querySelector document.body document.head window.getSelection window.getComputedStyle window.addEventListener new ResizeObserver new MutationObserver contentDocument contentWindow editor-canvas createPortal ``` Each occurrence is not automatically wrong. Classify whether it targets the shell or canvas, then derive the intended document explicitly. ## Test matrix 1. post and page editors; 2. custom post type with REST/block-editor support; 3. classic and block themes; 4. empty content and content containing legacy/API-v2 blocks; 5. sidebar/toolbar UI and canvas interaction together; 6. selection, keyboard, overlay positioning, scroll, zoom, RTL; 7. editor navigation/remount without duplicate listeners; 8. production CORS/CSP with media processing enabled. ## Critical rules - Treat editor shell and canvas as separate documents. - Derive canvas DOM APIs from an owned element's `ownerDocument` and `defaultView`. - Clean up every document/window observer and listener on ref teardown. - Use block metadata and editor hooks for assets; do not spray styles into `document.head`. - Prefer stable WordPress editor APIs over private DOM classes. - Never use a hardcoded iframe selector as the integration contract. ## Cross-references - Use **`wp-plugin-assets-loading`** for PHP enqueue contracts. - Use **`wp-client-side-media-processing`** for DIP, CORS, CSP, and WASM effects. - Use **`wp-admin-postbox-sortable`** for classic metabox/postbox behavior outside the canvas contract. ## References - Read `references/migration-patterns.md` for before/after DOM patterns and asset placement. - Iframed editor changes dev note: <https://make.wordpress.org/core/2026/08/03/iframed-editor-changes-in-wordpress-7-1/> - Persistent toolbar dev note: <https://make.wordpress.org/core/2026/07/13/consistent-navigation-in-wordpress-7-1-with-persistent-toolbar/> - Core sources: `wp-includes/script-loader.php`, block editor package builds, and block metadata asset registration.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.