Skip to content

URLInput: Skip search requests while an IME composition is in progress - #80602

Merged
jorgefilipecosta merged 4 commits into
trunkfrom
fix/72364-url-input-ime-composition
Aug 5, 2026
Merged

URLInput: Skip search requests while an IME composition is in progress#80602
jorgefilipecosta merged 4 commits into
trunkfrom
fix/72364-url-input-ime-composition

Conversation

@jorgefilipecosta

Copy link
Copy Markdown
Member

What?

Fixes #72364.

  • Track IME composition state in URLInput via explicit compositionstart/compositionend handlers on the input.
  • While composing, the typed value still propagates (the field keeps updating) but the debounced suggestions fetch is skipped.
  • On compositionend, fetch suggestions once with the confirmed value.
  • Adds Jest coverage for URLInput composition handling and a Chromium (CDP-driven) IME regression test to the Links e2e suite.

Why?

When typing with an IME (Japanese, Chinese, Korean, …), every composition update fires a change event, so URLInput (and the Link UI built on it) sends /wp/v2/search requests for intermediate values while the user is still composing. Requests should wait until the composition is confirmed. This mirrors the composition handling already used by rich text (packages/rich-text/src/hook/event-listeners/input-and-selection.js).

Testing Instructions

Automated

npm run test:unit packages/block-editor/src/components/url-input
npm run test:unit packages/block-editor/src/components/link-control
npm run test:e2e -- test/e2e/specs/editor/blocks/links.spec.js

To see the failure without the fix:

git checkout trunk -- packages/block-editor/src/components/url-input/index.js
npm run test:unit packages/block-editor/src/components/url-input # fails
git checkout HEAD -- packages/block-editor/src/components/url-input/index.js

Manual

  1. Enable a Japanese IME (macOS: System Settings → Keyboard → Input Sources → add Japanese – Romaji).
  2. Open a post, add a Paragraph block with some text, select a word and press the Link button in the block toolbar.
  3. Open DevTools → Network and filter requests by search.
  4. Switch the input source to Japanese and type e.g. honda (ほんだ) in the link field WITHOUT confirming the composition: on trunk each keystroke fires search requests; on this branch nothing fires while the underlined composition text is active.
  5. Press Enter to confirm the composition: the search fires once for the confirmed value and suggestions appear.
  6. Switch back to a Latin keyboard and type normally: suggestions still appear as you type, as before.

AI usage disclosure: fix and description drafted with AI assistance and reviewed by me.

@github-actions github-actions Bot added the [Package] Block editor /packages/block-editor label Jul 22, 2026
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Size Change: +49 B (0%)

Total Size: 7.81 MB

📦 View Changed
Filename Size Change
build/scripts/block-editor/index.min.js 434 kB +49 B (+0.01%)

compressed-size-action

@jorgefilipecosta
jorgefilipecosta marked this pull request as ready for review July 22, 2026 21:23
@github-actions

Copy link
Copy Markdown

Warning: Type of PR label mismatch

To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.

  • Required label: Any label starting with [Type].
  • Labels found: [Package] Block editor.

Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: jorgefilipecosta <jorgefilipecosta@git.wordpress.org>
Co-authored-by: Mamaduka <mamaduka@git.wordpress.org>
Co-authored-by: fumikito <takahashi_fumiki@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@jorgefilipecosta jorgefilipecosta added the [Type] Bug An existing feature does not function as intended label Jul 23, 2026
@jorgefilipecosta
jorgefilipecosta force-pushed the fix/72364-url-input-ime-composition branch 2 times, most recently from d42da7e to f155272 Compare July 27, 2026 17:53
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Flaky tests detected in 629cfc5.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/30402410551
📝 Reported issues:

@jorgefilipecosta
jorgefilipecosta force-pushed the fix/72364-url-input-ime-composition branch 2 times, most recently from 629cfc5 to eb0af2b Compare July 29, 2026 21:22
Comment on lines +96 to +100
// Whether characters are currently being composed with an IME. Composition
// state is tracked with explicit `compositionstart` and `compositionend`
// listeners because the `isComposing` property of native events is not
// reliable across browsers (e.g. Safari).
const isComposingRef = useRef( false );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using state is probably better for this case, then fetch effect remains in a single place that handles suggestions, making handleCompositionEnd a simple callback.

I would also drop the comment.

function handleCompositionEnd() {
	setIsComposing( false );
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, with the composition state as a dependency the fetch logic stays in a single place. The code was updated as suggested. Let me know if this looks good 👍

Comment thread packages/block-editor/src/components/url-input/index.js Outdated
} );
} );

describe( 'IME composition', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of refactored tests, should cover most cases and compliment the e2e test.

Tests snippet

it( 'should not fetch suggestions for the intermediate values of an IME composition', async () => {
	const { input } = renderURLInput();

	fireEvent.compositionStart( input );
	fireEvent.change( input, { target: { value: 'ほ' } } );
	fireEvent.change( input, { target: { value: 'ほん' } } );
	fireEvent.change( input, { target: { value: 'ほんだ' } } );
	await flushDebounce();

	expect( fetchLinkSuggestions ).not.toHaveBeenCalled();
	expect( screen.queryByRole( 'listbox' ) ).not.toBeInTheDocument();
} );

it( 'should not fetch suggestions for a value superseded by an IME composition', async () => {
	const { user, input } = renderURLInput();

	await user.type( input, 'ab' );
	fireEvent.compositionStart( input );
	fireEvent.change( input, { target: { value: 'abほ' } } );
	await flushDebounce();

	expect( fetchLinkSuggestions ).not.toHaveBeenCalled();
} );

// Firefox reports the confirmed value of a composition after
// `compositionend`, Chrome and Safari before it.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1305387
it.each( [ 'before', 'after' ] )(
	'should fetch suggestions for a composed value reported %s the composition ends',
	async ( order ) => {
		const { input } = renderURLInput();

		fireEvent.compositionStart( input );
		fireEvent.change( input, { target: { value: 'ほんだ' } } );
		// Compositions outlast the debounce, so the confirmed value is
		// the only one a request is made for.
		await flushDebounce();

		if ( order === 'before' ) {
			fireEvent.change( input, { target: { value: 'ホンダ' } } );
			fireEvent.compositionEnd( input );
		} else {
			fireEvent.compositionEnd( input );
			fireEvent.change( input, { target: { value: 'ホンダ' } } );
		}

		expect( await screen.findByRole( 'listbox' ) ).toBeVisible();
		expect( fetchLinkSuggestions ).toHaveBeenCalledTimes( 1 );
		expect( fetchLinkSuggestions ).toHaveBeenCalledWith( 'ホンダ', {
			isInitialSuggestions: false,
		} );
	}
);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the tests! They were applied as suggested, I think covering both event orders in a single parameterized test is better 👍

@jorgefilipecosta
jorgefilipecosta force-pushed the fix/72364-url-input-ime-composition branch from 99a49ba to 418ec3d Compare August 5, 2026 16:03
Co-authored-by: George Mamadashvili <georgemamadashvili@gmail.com>

@Mamaduka Mamaduka left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the follow-ups, @jorgefilipecosta!

@jorgefilipecosta
jorgefilipecosta merged commit 61e4398 into trunk Aug 5, 2026
47 of 48 checks passed
@jorgefilipecosta
jorgefilipecosta deleted the fix/72364-url-input-ime-composition branch August 5, 2026 17:05
@github-actions github-actions Bot added this to the Gutenberg 23.8 milestone Aug 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[Package] Block editor /packages/block-editor [Type] Bug An existing feature does not function as intended

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URLInput search fires too frequently, causing unnecessary requests (internationalization input issue)

2 participants