Make WordPress Core

Opened 3 hours ago

Last modified 3 hours ago

#65958 new defect (bug)

Reordering a meta box destroys any TinyMCE editor inside it

Reported by: sanket.parmar Owned by:
Priority: normal Milestone: Awaiting Review
Component: Administration Version: 7.1
Severity: major Keywords: has-patch
Cc: Focuses:

Description

Pressing a meta box's Move up / Move down arrow destroys any TinyMCE editor rendered inside that box with wp_editor(). The editor goes blank on screen, its content becomes unreachable, and switching Visual/Text afterwards throws an uncaught TypeError.

This is core-only. It needs no plugins and does not depend on the block editor.

Cause

postboxes.handleOrderBetweenSortables() relocates the box across containers with detach() + appendTo()/prependTo() — see wp-admin/js/postbox.js lines 181–189. Re-inserting a subtree that contains an <iframe> makes the browser give that iframe a brand-new, empty document. TinyMCE keeps a reference to the discarded one, so editor.getDoc() and iframe.contentDocument are no longer the same object, and every subsequent operation acts on a document that is not on screen.

handleOrder() also reorders within a single container using .before() and .after() (lines 129 and 143). That is the same re-insertion, so it should break the same way — I have not tested that path.

Note the guard at line 173:

// Return if there's only one visible sortables area, e.g. in the block editor page.

On a current block editor screen there are three visible sortables areas (normal-sortables, advanced-sortables, side-sortables), so the guard does not fire and a lone meta box hops containers on the first arrow press.

Steps to reproduce

  1. Drop this in wp-content/mu-plugins/:
<?php
add_action( 'add_meta_boxes', function () {
        add_meta_box( 'my-custom-metabox', 'Custom Meta', 'demo_metabox_cb' );
} );

function demo_metabox_cb( $post ) {
        wp_editor( get_post_meta( $post->ID, 'my-custom-meta', true ), 'my-custom-meta' );
}
  1. Open Posts → Add New, expand the Meta Boxes panel, and type some text into the Custom Meta editor on the Visual tab.
  2. Press the meta box's Move down arrow.
  3. The editor is now blank.
  4. Click Text, then Visual.

Expected

Reordering a meta box leaves the editors inside it working, with their content intact.

Actual

The editor is blank from step 3 on, and step 5 throws:

Uncaught TypeError: Cannot read properties of null (reading 'setBaseAndExtent')
    at Object.select (tinymce.min.js:2:293163)
    at _ (editor.min.js?ver=7.1:2:5063)
    at n (editor.min.js?ver=7.1:2:1731)

Measured either side of step 3 with:

const ed  = tinymce.get( 'my-custom-meta' );
const ifr = document.getElementById( 'my-custom-meta_ifr' );
( {
        parent:     document.getElementById( 'my-custom-metabox' ).parentElement.id,
        sameDoc:    ed.getDoc() === ifr.contentDocument,
        iframeBody: ifr.contentDocument.body.innerHTML,
        content:    ed.getContent(),
} )
' Before After
postbox parent normal-sortables advanced-sortables
getDoc() === contentDocument true false
iframe body <p>…</p> ""
getContent() <p>…</p> <p>…</p> (stranded)

Environment

WordPress 7.1, Chrome 151, macOS. Reproduced both with the block editor and with Classic Editor 1.7.0 and classic-editor-replace = classic, i.e. with the block editor entirely out of the page.

Very likely the underlying cause of the setBaseAndExtent reports on #62450. That ticket is framed as a Firefox-only failure to initialise on the Visual tab, but the comments that reopened it describe this exact symptom — content disappearing on Visual/Text switching with can't access property "setBaseAndExtent", n is null — from users of ACF/SCF/JetEngine WYSIWYG fields, which render wp_editor() inside meta boxes and relocate their wrappers in the DOM.

I have not shown that this explains the original Firefox initialise-on-Visual report, and I have only tested Chrome, so I am filing separately rather than assuming they are one bug.

Originally surfaced from Gutenberg #74627.

Change History (2)

This ticket was mentioned in PR #13262 on WordPress/wordpress-develop by @sanket.parmar.


3 hours ago
#1

  • Keywords has-patch added

## Trac ticket

https://core.trac.wordpress.org/ticket/65958

## What

Moving a meta box re-inserts its DOM subtree; when the box holds a wp_editor() TinyMCE editor, the re-inserted iframe is reset and TinyMCE keeps a stale reference, so the editor goes blank and the next Visual/Code switch throws.

postbox.js now fires the existing postbox-moved event on the Move up/down paths (it already fires on cross-area drag). A listener in editor.js re-initialises each editor in the moved box — save content, remove the stale instance, re-init in the same mode — keeping postbox.js editor-agnostic.

## Test

  • [x] grunt jshint:core — 98 files lint free
  • [x] Register a wp_editor() meta box, type in Visual, Move up/down: editor keeps its content, getDoc() === iframe.contentDocument stays true, no console error
  • [x] After the move, switch Code then Visual: no setBaseAndExtent TypeError
  • [ ] Same, in Firefox

## Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 4.8
Used for: root-cause analysis, implementation, and local verification

#2 @sanket.parmar
3 hours ago

Patch: https://github.com/WordPress/wordpress-develop/pull/13262

The iframe reset can't be prevented once the box is re-inserted, so the editors are re-hydrated around the move instead, and postbox.js stays editor-agnostic.

The move handlers (handleOrder and handleOrderBetweenSortables) now fire the existing postbox-moved event on the arrow paths too. A new listener in wp-admin/js/editor.js responds: for each wp_editor() in the moved box it saves the content, removes the stale TinyMCE instance, and re-initialises it from tinyMCEPreInit in the same mode. Code-mode editors just get the stale instance dropped so the next Visual switch re-initialises cleanly.

This covers the Move up / Move down arrows (both within an area and across areas) and the existing cross-area drag, which already fires postbox-moved.

Verified on a wp_editor() meta box: after reordering, the editor keeps its content and the Visual/Code switch no longer throws.

Note: See TracTickets for help on using tickets.