From f42dc58cd90c3de1d29598898d6cee8a230c45bb Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:05:50 -0600 Subject: [PATCH 1/3] fix: let a failed oEmbed request fail `transformOEmbedApiResponse` caught every failed oEmbed request and resolved with a made-up response holding a link to the URL. Core then stored that as a finished preview. A block that polls for its preview never saw the failure. After a VideoPress upload, the first `/oembed/1.0/proxy` request returns `404` until WordPress.com can embed the new video. The block took the link for a preview, stopped asking, and rendered it in its player sandbox: an empty box, with the link saved into the block's `cacheHtml`. The middleware now leaves a failed request to reject, so core stores `false`, as it does everywhere else. The core Embed block is unaffected: it treats `false` and a link fallback the same way and shows "could not be embedded" for both. A link fallback that WordPress itself returns still passes through, and the wrapper stripping for YouTube, Vimeo, Dailymotion and TED embeds is unchanged. --- src/utils/api-fetch.js | 69 +++++++++++++---------------------- src/utils/api-fetch.test.js | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 44 deletions(-) diff --git a/src/utils/api-fetch.js b/src/utils/api-fetch.js index d136c3a38..370aaead4 100644 --- a/src/utils/api-fetch.js +++ b/src/utils/api-fetch.js @@ -1,5 +1,4 @@ import apiFetch from '@wordpress/api-fetch'; -import { getQueryArg } from '@wordpress/url'; import { __ } from '@wordpress/i18n'; import { getGBKit, POST_FALLBACKS } from './bridge'; import { info, warn, error as logError } from './logger'; @@ -454,56 +453,38 @@ function mediaPermissionsMiddleware( options, next ) { * Remove the wrapping element from the oEmbed response, as it breaks * Gutenberg's sizing styles. * + * A failed request is left to reject. Core stores `false` for it, which the + * Embed block shows as "could not be embedded" and which blocks that poll for a + * preview — VideoPress, while WordPress.com is still processing an upload — + * take as the cue to ask again. Resolving with a link in its place reads as a + * finished preview, so those blocks stop asking and render the link. + * * @type {APIFetchMiddleware} * * @todo Hoist this host-specific logic to the host app. */ function transformOEmbedApiResponse( options, next ) { if ( options.path && options.path.indexOf( 'oembed' ) !== -1 ) { - const url = getQueryArg( options.path, 'url' ); - const response = next( options, next ); - - /** - * Creates an embed response emulating core's fallback link. - */ - function createFallbackResponse() { - const link = document.createElement( 'a' ); - link.href = url; - link.innerText = url; - return { - html: link.outerHTML, - type: 'rich', - provider_name: 'Embed', - }; - } - - return new Promise( ( resolve ) => { - response - .then( ( data ) => { - if ( data.html ) { - /** - * Removes wrappers from YouTube, Vimeo, Dailymotion, TED block, e.g. - * ,
,
,
- * and return just the ', + type: 'video', + } ); + + const preview = await apiFetch( { path: proxyPath } ); + + expect( preview.html ).toBe( + '' + ); + } ); + + it( 'returns other embed markup as WordPress sent it', async () => { + const html = + 'https://example.com/post'; + respondWith( { html, type: 'rich' } ); + + const preview = await apiFetch( { path: proxyPath } ); + + expect( preview ).toEqual( { html, type: 'rich' } ); + } ); + } ); } ); From 8a8ef62fdcd23bead34c43bd59b792b3cccf176f Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:49:31 -0600 Subject: [PATCH 2/3] test: check the stored preview for a non-embeddable URL The E2E test for a non-embeddable URL was named for a fallback link the editor no longer makes up, and it waited only for the block's `url` attribute. The Embed block sets that on submit, before the oEmbed request settles, so the test passed whether or not a failed request was replaced with a made-up response. It now waits for core to store `false` for the preview. With the middleware's catch restored, it times out there. --- e2e/embed-content.spec.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/e2e/embed-content.spec.js b/e2e/embed-content.spec.js index adb56fe09..fe870d6cd 100644 --- a/e2e/embed-content.spec.js +++ b/e2e/embed-content.spec.js @@ -107,7 +107,7 @@ test.describe( 'Embedded Content', () => { expect( blocks[ 0 ].attributes.providerNameSlug ).toBe( 'youtube' ); } ); - test( 'should show a fallback link for a non-embeddable URL', async ( { + test( 'should store no preview for a non-embeddable URL', async ( { page, } ) => { const editor = new EditorPage( page ); @@ -123,16 +123,13 @@ test.describe( 'Embedded Content', () => { await urlInput.fill( 'https://example.com/not-embeddable' ); await page.keyboard.press( 'Enter' ); - // Wait for the embed to resolve (or fall back). - // The block should still render with the URL attribute. + // The proxy answers 404, so core stores `false` for the preview + // rather than a preview object. await page.waitForFunction( - () => { - const blocks = window.wp.data - .select( 'core/block-editor' ) - .getBlocks(); - return blocks[ 0 ]?.attributes?.url; - }, - null, + ( url ) => + window.wp.data.select( 'core' ).getEmbedPreview( url ) === + false, + 'https://example.com/not-embeddable', { timeout: 30_000 } ); From e3a402819053fd240bb9ff155434a6966d9af04a Mon Sep 17 00:00:00 2001 From: Jeremy Massel <1123407+jkmassel@users.noreply.github.com> Date: Tue, 6 Oct 2026 16:49:31 -0600 Subject: [PATCH 3/3] docs: trim the oEmbed middleware's comment to what it does The comment also argued against the link fallback, which is gone. That argument is in the commit that removed it. --- src/utils/api-fetch.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/utils/api-fetch.js b/src/utils/api-fetch.js index 370aaead4..d08aa9431 100644 --- a/src/utils/api-fetch.js +++ b/src/utils/api-fetch.js @@ -453,11 +453,8 @@ function mediaPermissionsMiddleware( options, next ) { * Remove the wrapping element from the oEmbed response, as it breaks * Gutenberg's sizing styles. * - * A failed request is left to reject. Core stores `false` for it, which the - * Embed block shows as "could not be embedded" and which blocks that poll for a - * preview — VideoPress, while WordPress.com is still processing an upload — - * take as the cue to ask again. Resolving with a link in its place reads as a - * finished preview, so those blocks stop asking and render the link. + * A failed request is left to reject, so core stores `false` and blocks that + * poll for a preview (e.g. VideoPress while processing an upload) ask again. * * @type {APIFetchMiddleware} *