LinkControl: Prepend https:// to URLs on direct entry#75379
LinkControl: Prepend https:// to URLs on direct entry#75379
Conversation
Ensures consistent URL normalization by prepending https:// to domain names when users press Enter without selecting a suggestion. Previously, URLs like "wordpress.org" were submitted without a protocol, while selecting the suggestion would correctly prepend https://. Refactored to use a shared normalizeDirectEntryURL() function for both suggestion selection and direct submission paths, eliminating code duplication and ensuring consistent behavior.
|
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.
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. |
|
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 If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
There was a problem hiding this comment.
Pull request overview
This PR fixes inconsistent URL normalization in LinkControl so that direct URL entry (pressing Enter without selecting a suggestion) is normalized the same way as suggestion selection—specifically ensuring https:// is prepended where appropriate.
Changes:
- Introduced a shared URL normalization helper and applied it to both direct-entry suggestions and Enter-key submissions.
- Updated
LinkControlSearchInputto normalize direct submissions before selection handling. - Added/updated unit tests and expanded
LinkControlREADME documentation around URL normalization behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
packages/block-editor/src/components/link-control/use-search-handler.js |
Adds normalizeDirectEntryURL() and uses it in the direct-entry suggestion flow. |
packages/block-editor/src/components/link-control/search-input.js |
Normalizes the URL when submitting via Enter without selecting a suggestion. |
packages/block-editor/src/components/link-control/test/index.js |
Adds unit tests for direct-entry normalization and updates URL validation expectations. |
packages/block-editor/src/components/link-control/README.md |
Documents auto-prepending https:// for protocol-less domains and other preserved URL forms. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * Handles special protocols (mailto, tel), hash links, and relative paths. | ||
| * | ||
| * This is the single source of truth for URL normalization across LinkControl. | ||
| * Used by both handleDirectEntry (suggestion flow) and direct submission (Enter key). | ||
| * | ||
| * @param {string} val The URL value to normalize | ||
| * @return {string} The normalized URL with protocol if needed | ||
| */ | ||
| export function normalizeDirectEntryURL( val ) { | ||
| let type = URL_TYPE; | ||
|
|
||
| const protocol = getProtocol( val ) || ''; | ||
|
|
||
| if ( protocol.includes( 'mailto' ) ) { | ||
| type = MAILTO_TYPE; | ||
| } | ||
|
|
||
| if ( protocol.includes( 'tel' ) ) { | ||
| type = TEL_TYPE; | ||
| } | ||
|
|
||
| if ( val?.startsWith( '#' ) ) { | ||
| type = INTERNAL_TYPE; | ||
| } | ||
|
|
||
| // Only prepend https:// for standard URLs without valid protocols | ||
| return type === URL_TYPE ? prependHTTPS( val ) : val; |
There was a problem hiding this comment.
normalizeDirectEntryURL() duplicates the protocol/type detection logic that already exists in handleDirectEntry(). This makes it easy for the two implementations to drift (e.g., if a new type like relative path handling is added in one place but not the other). Consider refactoring to compute the type once and reuse it (e.g., a helper that returns { type, url }), or simplify normalizeDirectEntryURL() to just call prependHTTPS(val) since prependHTTPS already preserves mailto:, tel:, #, and relative paths.
| * Handles special protocols (mailto, tel), hash links, and relative paths. | |
| * | |
| * This is the single source of truth for URL normalization across LinkControl. | |
| * Used by both handleDirectEntry (suggestion flow) and direct submission (Enter key). | |
| * | |
| * @param {string} val The URL value to normalize | |
| * @return {string} The normalized URL with protocol if needed | |
| */ | |
| export function normalizeDirectEntryURL( val ) { | |
| let type = URL_TYPE; | |
| const protocol = getProtocol( val ) || ''; | |
| if ( protocol.includes( 'mailto' ) ) { | |
| type = MAILTO_TYPE; | |
| } | |
| if ( protocol.includes( 'tel' ) ) { | |
| type = TEL_TYPE; | |
| } | |
| if ( val?.startsWith( '#' ) ) { | |
| type = INTERNAL_TYPE; | |
| } | |
| // Only prepend https:// for standard URLs without valid protocols | |
| return type === URL_TYPE ? prependHTTPS( val ) : val; | |
| * Delegates to prependHTTPS, which preserves special protocols (mailto, tel), | |
| * hash links, and relative paths. | |
| * | |
| * @param {string} val The URL value to normalize | |
| * @return {string} The normalized URL with protocol if needed | |
| */ | |
| export function normalizeDirectEntryURL( val ) { | |
| return prependHTTPS( val ); |
There was a problem hiding this comment.
I agree. I think this is increasing the level of complexity rather than finding a way to simplify it. I think validation and normalization can happen in one flow.
| - `mailto` protocol links (eg: `mailto: hello@wordpress.org`) | ||
| - Relative paths (`/page`, `./page`, `../page`) | ||
|
|
||
| When a URL without a valid protocol is submitted (either by pressing Enter or clicking Apply), the component automatically prepends `https://` to ensure valid links. Special protocols (mailto:, tel:), hash links, and relative paths are preserved as-is. |
There was a problem hiding this comment.
The README now claims URLs without a protocol are normalized when submitted “either by pressing Enter or clicking Apply”, but the Apply button submission path in LinkControl uses the raw currentUrlInputValue (only validation uses prependHTTPS). Either update the docs to only describe the Enter/suggestion behavior, or update the Apply/submit path to normalize (e.g., by applying the same normalization helper before calling onChange).
| When a URL without a valid protocol is submitted (either by pressing Enter or clicking Apply), the component automatically prepends `https://` to ensure valid links. Special protocols (mailto:, tel:), hash links, and relative paths are preserved as-is. | |
| When a URL without a valid protocol is submitted via the input (for example, by pressing Enter), the component automatically prepends `https://` to ensure valid links. Special protocols (mailto:, tel:), hash links, and relative paths are preserved as-is. |
| import useSearchHandler from './use-search-handler'; | ||
| import { normalizeDirectEntryURL } from './use-search-handler'; |
There was a problem hiding this comment.
This adds a second import from the same module (./use-search-handler). The repo’s ESLint config enables import/no-duplicates, so this will fail linting. Combine these into a single import (default + named) from ./use-search-handler.
| import useSearchHandler from './use-search-handler'; | |
| import { normalizeDirectEntryURL } from './use-search-handler'; | |
| import useSearchHandler, { normalizeDirectEntryURL } from './use-search-handler'; |
|
Size Change: +25 B (0%) Total Size: 3 MB
ℹ️ View Unchanged
|
jeryj
left a comment
There was a problem hiding this comment.
Can we add normalization to the validation flow instead of having multiple routes of validation and changing urls that can drift in implementation? I think we should be looking for a way to simplify rather than adding to the complexity, if possible.
| ); | ||
| } ); | ||
|
|
||
| it( 'should NOT prepend https:// to relative paths', async () => { |
There was a problem hiding this comment.
I think all these "Should not prepend https://" tests should get refactored to one looping test.
| * Handles special protocols (mailto, tel), hash links, and relative paths. | ||
| * | ||
| * This is the single source of truth for URL normalization across LinkControl. | ||
| * Used by both handleDirectEntry (suggestion flow) and direct submission (Enter key). | ||
| * | ||
| * @param {string} val The URL value to normalize | ||
| * @return {string} The normalized URL with protocol if needed | ||
| */ | ||
| export function normalizeDirectEntryURL( val ) { | ||
| let type = URL_TYPE; | ||
|
|
||
| const protocol = getProtocol( val ) || ''; | ||
|
|
||
| if ( protocol.includes( 'mailto' ) ) { | ||
| type = MAILTO_TYPE; | ||
| } | ||
|
|
||
| if ( protocol.includes( 'tel' ) ) { | ||
| type = TEL_TYPE; | ||
| } | ||
|
|
||
| if ( val?.startsWith( '#' ) ) { | ||
| type = INTERNAL_TYPE; | ||
| } | ||
|
|
||
| // Only prepend https:// for standard URLs without valid protocols | ||
| return type === URL_TYPE ? prependHTTPS( val ) : val; |
There was a problem hiding this comment.
I agree. I think this is increasing the level of complexity rather than finding a way to simplify it. I think validation and normalization can happen in one flow.
|
@jeryj Yes let's try and combine
...and have that work consistently across both manual submission of values and selection of suggestions. I think we need to get this in place for 7.0 as it's the cause of edge cases. |
|
@getdave I addressed validation and normalization to https:// in #75488. I was wrong about getting it all in one flow though. The main issue I ran into was that we use an initial So, if we normalize and then validate, all single strings pass when they would have previously failed. So, in 75488 I chose to validate using the same flow, but always normalize after validation so I didn't introduce a new standard of what was accepted. I'm closing this PR to help with triage, but feel free to open it back up if I'm wrong! |
What
Fixes #75374
Ensures LinkControl consistently prepends
https://to domain names when users press Enter without selecting a suggestion, matching the behavior when suggestions are selected.Why
Previously, typing
wordpress.organd pressing Enter would save the URL without a protocol, while selecting the suggestion would correctly prependhttps://. This inconsistency created broken links and confused user expectations.The root cause was that the direct submission path (pressing Enter) and the suggestion selection path used different code for URL normalization, leading to inconsistent behavior.
How
Refactored URL normalization to use a single source of truth:
Extracted shared function: Created
normalizeDirectEntryURL()inuse-search-handler.jsthat handles all URL normalization logic (prependinghttps://while preservingmailto:,tel:, hash links, and relative paths)Applied to both paths: Both the suggestion selection flow and direct Enter submission now call the same normalization function, ensuring consistent behavior
Comprehensive test coverage: Added 7 new unit tests covering domain names, www prefixes, special protocols, hash links, relative paths, and URLs with existing protocols
Updated documentation: Enhanced README to clearly document the automatic
https://prepending behaviorTesting Instructions
Manual Testing
wordpress.org(without https://) and press Enterhttps://wordpress.orgwww.wordpress.org→ should becomehttps://www.wordpress.orgmailto:test@example.com→ should stay asmailto:test@example.comtel:123456789→ should stay astel:123456789#anchor→ should stay as#anchor/relative/path→ should stay as/relative/pathAutomated Testing
All existing unit tests pass (134 total), including 7 new tests specifically for this fix. E2E tests for buttons and navigation blocks continue to pass.