Skip to content

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

@fumikito

Description

@fumikito

Description

This issue affects users who type using IME (Input Method Editor), commonly used for Japanese, Chinese, Korean, and other non-Latin languages. While entering text in the link URLInput component, search requests are fired during IME composition, before the user confirms the input. This leads to a poor editing experience and unnecessary API requests.

This appears to be an internationalization (i18n) usability issue that impacts many languages that rely on composition input.

We currently use a workaround plugin but believe this should be solved in core to improve accessibility and editor performance globally.

Expected Behavior

  • Search should wait until IME composition ends (compositionend)
  • UI should avoid firing network requests for intermediate characters
  • Better user experience for Japanese, Chinese, and Korean users

Possible Solution (optional, for discussion)

  • Skip search while composing (compositionstart/compositionend)
  • Delay search until confirmed input
  • Consider increasing debounce timing
  • Add filter for debounce timing.

Step-by-step reproduction instructions

  1. Open the Block Editor
  2. Insert a Paragraph block
  3. Click the Link button
  4. Type Japanese text using IME, e.g. ほんだ → convert to ホンダ
  5. Open DevTools → Network
  6. Observe: multiple /wp/v2/search requests sent during typing

Screenshots, screen recording, code snippet

urlinput.mp4

We currently use a workaround plugin but believe this should be solved in core to improve accessibility and editor performance globally.

/**
 * IME tweaks and debounce control for Block Editor's Link UI.
 *
 * @package IncSearchDelay
 */

( function( wp ) {
	'use strict';

	if ( ! wp || ! wp.apiFetch || ! wp.domReady ) {
		return;
	}

	// Get options from PHP(wp_localize_script)
	const settings = window.incSearchDelaySettings || {
		debounceMs: 500,
		cacheDuration: 30000,
		minSearchLength: 2
	};

	// IME status
	let isComposing = false;

	/**
	 * IME: Listen compositionstart/end events.
	 */
	wp.domReady( function() {
		// compositionstart:
		document.addEventListener( 'compositionstart', function( e ) {
			if ( e.target.id && e.target.id.startsWith( 'url-input-control-' ) ) {
				isComposing = true;
			}
		}, true );

		// compositionend
		document.addEventListener( 'compositionend', function( e ) {
			if ( e.target.id && e.target.id.startsWith( 'url-input-control-' ) ) {
				isComposing = false;
				// After IME composition, fires input event and do search.
				const event = new Event( 'input', { bubbles: true } );
				e.target.dispatchEvent( event );
			}
		}, true );
	} );

	/**
	 * Cache store of the search results.
	 */
	const searchCache = new Map();

	/**
	 * debounce timer
	 */
	const pendingRequests = new Map();

	/**
	 * apiFetch middleware: Optimize requests for /wp/v2/search
	 */
	wp.apiFetch.use( function( options, next ) {
		// Only affects for /wp/v2/search
		if ( ! options.path || ! options.path.includes( '/wp/v2/search' ) ) {
			return next( options );
		}

		// Do nothing while IME compositing.
		if ( isComposing ) {
			return Promise.resolve( [] );
		}

		const cacheKey = options.path;
		const cached = searchCache.get( cacheKey );

		// If a cache exists, return one.
		if ( cached && ( Date.now() - cached.timestamp < settings.cacheDuration ) ) {
			return Promise.resolve( cached.data );
		}

		// Cancel existing timer.
		const existingTimer = pendingRequests.get( cacheKey );
		if ( existingTimer ) {
			clearTimeout( existingTimer.timer );
			existingTimer.reject( new Error( 'Cancelled by new request' ) );
		}

		// debounce
		return new Promise( function( resolve, reject ) {
			const timer = setTimeout( function() {
				pendingRequests.delete( cacheKey );

				next( options )
					.then( function( response ) {
						// Cache results.
						searchCache.set( cacheKey, {
							data: response,
							timestamp: Date.now()
						} );
						resolve( response );
					} )
					.catch( function( error ) {
						// Ignore cancelled requests.
						if ( error.message !== 'Cancelled by new request' ) {
							reject( error );
						}
					} );
			}, settings.debounceMs );

			// Save a timer for cancelation.
			pendingRequests.set( cacheKey, { timer, reject } );
		} );
	} );
} )( window.wp );

Environment info

  • WordPress 6.8.3
  • Google Chrome

Please confirm that you have searched existing issues in the repo.

  • Yes

Please confirm that you have tested with all plugins deactivated except Gutenberg.

  • Yes

Please confirm which theme type you used for testing.

  • Block
  • Classic
  • Hybrid (e.g. classic with theme.json)
  • Not sure

Metadata

Metadata

Assignees

No one assigned

    Labels

    Internationalization (i18n)Issues or PRs related to internationalization efforts[Feature] Link EditingLink components (LinkControl, URLInput) and integrations (RichText link formatting)[Type] BugAn existing feature does not function as intended

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions