Skip to content

[lexical] Fix: exclude Android WebView from IS_SAFARI browser detection#8267

Merged
etrepum merged 1 commit into
facebook:mainfrom
kzroo:fix/android-webview-safari-detection
Mar 28, 2026
Merged

[lexical] Fix: exclude Android WebView from IS_SAFARI browser detection#8267
etrepum merged 1 commit into
facebook:mainfrom
kzroo:fix/android-webview-safari-detection

Conversation

@kzroo

@kzroo kzroo commented Mar 28, 2026

Copy link
Copy Markdown
Contributor

Bug

Android WebView's user agent string contains Version/4.0 ... Safari/537.36, which causes the IS_SAFARI regex in packages/shared/src/environment.ts to match. This means Lexical thinks it's running in Safari when it's actually running in an Android WebView.

This affects any app embedding Lexical inside an Android WebView — including apps built with Capacitor, Cordova, React Native WebView, or any custom WebView wrapper.

What goes wrong

When IS_SAFARI is incorrectly true on Android, three things break:

  1. Ghost characters after deleting text: COMPOSITION_SUFFIX uses a non-breaking space (\u00A0, the Safari path) instead of a zero-width space (\u200b, the Android path). The non-breaking space is visible and persists in the DOM after the user deletes all text, leaving a "ghost" character that can't be removed.

  2. Broken backspace behavior: The Android-specific composition cleanup code in $updateTextNodeFromDOMContent is guarded by if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT) — so it gets skipped entirely. This means empty text nodes aren't cleaned up properly after composition ends, causing erratic cursor positioning and broken backspace.

  3. Wrong composition-end handling: The Safari-specific isSafariEndingComposition workaround activates, which defers keydown handling to prevent an extra character deletion in Safari. On Android this deferral causes the opposite problem — keystrokes are swallowed or applied to the wrong position.

The editor works perfectly in Chrome browser on Android (where the UA does not contain Version/). The bug only manifests inside WebViews.

Fix

Move IS_ANDROID before IS_SAFARI in packages/shared/src/environment.ts and add && !IS_ANDROID to exclude Android WebView from the Safari detection.

Test plan

  • Verified fix in a Capacitor app running Lexical in Android WebView
  • Confirmed no regression in Chrome browser on Android
  • Confirmed no regression on desktop (Safari, Chrome, Firefox)

Android WebView's user agent string contains "Version/4.0 ... Safari/537.36",
which causes the IS_SAFARI regex to match. This makes Lexical think it's
running in Safari when it's actually in an Android WebView.

When IS_SAFARI is incorrectly true on Android, three things break:

1. Ghost characters after deleting text — COMPOSITION_SUFFIX uses a
   non-breaking space (Safari path) instead of a zero-width space (Android
   path). The non-breaking space persists visibly after the user deletes
   all text.

2. Broken backspace — the Android-specific composition cleanup in
   $updateTextNodeFromDOMContent is guarded by !IS_SAFARI and gets skipped,
   so empty text nodes aren't cleaned up after composition ends.

3. Wrong composition-end handling — the Safari-specific
   isSafariEndingComposition workaround activates, deferring keydown
   handling in a way that swallows keystrokes on Android.

Fix: move IS_ANDROID before IS_SAFARI and add && !IS_ANDROID to exclude
Android WebView from the Safari detection.
@vercel

vercel Bot commented Mar 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
lexical Ready Ready Preview, Comment Mar 28, 2026 9:06am
lexical-playground Ready Ready Preview, Comment Mar 28, 2026 9:06am

Request Review

@meta-cla

meta-cla Bot commented Mar 28, 2026

Copy link
Copy Markdown

Hi @kzroo!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@meta-cla

meta-cla Bot commented Mar 28, 2026

Copy link
Copy Markdown

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks!

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Mar 28, 2026
@kzroo kzroo changed the title fix: exclude Android WebView from IS_SAFARI browser detection [lexical] Fix: exclude Android WebView from IS_SAFARI browser detection Mar 28, 2026
@etrepum etrepum added the extended-tests Run extended e2e tests on a PR label Mar 28, 2026
@etrepum etrepum enabled auto-merge March 28, 2026 15:12
@etrepum etrepum added this pull request to the merge queue Mar 28, 2026
Merged via the queue into facebook:main with commit d30ef9f Mar 28, 2026
38 checks passed
@etrepum etrepum mentioned this pull request Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. extended-tests Run extended e2e tests on a PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants