CVE-2026-113096
Overview
Files Changed
content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.javacontent/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.javacontent/public/android/junit/src/org/chromium/content/browser/input/TextSuggestionPopupControllerTest.java
Patch
From c3bc7c376dff5488ba22eb2e7312fadf3891070a Mon Sep 17 00:00:00 2001 From: Ted Choc <[email protected]> Date: Wed, 23 Sep 2026 09:13:50 -0700 Subject: [PATCH] Clamp vertical positioning of text suggestion popups Clamps the caret Y-offset within the container view bounds and handles NaN values when displaying spell check and text suggestion popups. Additionally, prevents SuggestionsPopupWindow from shifting above its parent view during vertical clipping. Bug: 562238089 Change-Id: I61a4495911e0ebb80b271baae26bc5b578aa3c68 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8431019 Reviewed-by: Yaron Friedman <[email protected]> Commit-Queue: Ted Choc <[email protected]> Cr-Commit-Position: refs/heads/main@{#1703506} --- diff --git a/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java b/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java index 96e29b1..3f8381f9 100644 --- a/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java +++ b/content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java @@ -365,7 +365,7 @@ positionX = Math.max(-mContentView.getPaddingLeft(), positionX); // Vertical clipping: if part of the menu or its bottom margin would fall off the bottom of - // the screen, shift it up to keep it on-screen. + // the screen, shift it up to keep it on-screen, without drawing above mParentView. positionY = Math.min( positionY, @@ -373,6 +373,7 @@ - height - mContentView.getPaddingTop() - mPopupVerticalMargin); + positionY = Math.max(positionY, positionInWindow[1] - mContentView.getPaddingTop()); mPopupWindow.showAtLocation(mParentView, Gravity.NO_GRAVITY, positionX, positionY); } diff --git a/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.java b/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.java index 0f4c88f..2475c812 100644 --- a/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.java +++ b/content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.java @@ -8,6 +8,8 @@ import android.content.Context; +import androidx.annotation.VisibleForTesting; + import org.jni_zero.CalledByNative; import org.jni_zero.JNINamespace; @@ -130,8 +132,14 @@ } } - private float getContentOffsetYPix() { - return mWebContents.getRenderCoordinates().getContentOffsetYPix(); + @VisibleForTesting + double calculateYOffset(double caretYPx) { + float contentOffsetYPix = mWebContents.getRenderCoordinates().getContentOffsetYPix(); + if (Double.isNaN(caretYPx)) return contentOffsetYPix; + + int containerHeight = assumeNonNull(mViewDelegate.getContainerView()).getHeight(); + double maxOffset = Math.max((double) contentOffsetYPix, (double) containerHeight); + return Math.clamp(caretYPx + contentOffsetYPix, contentOffsetYPix, maxOffset); } // WindowEventObserver @@ -222,7 +230,7 @@ assumeNonNull(mViewDelegate.getContainerView())); mSpellCheckPopupWindow.show( - caretXPx, caretYPx + getContentOffsetYPix(), markedText, suggestions); + caretXPx, calculateYOffset(caretYPx), markedText, suggestions); } @CalledByNative @@ -263,7 +271,7 @@ assumeNonNull(mViewDelegate.getContainerView())); mTextSuggestionsPopupWindow.show( - caretXPx, caretYPx + getContentOffsetYPix(), markedText, suggestions); + caretXPx, calculateYOffset(caretYPx), markedText, suggestions); } /** Hides the text suggestion menu (and informs Blink that it was closed). */ diff --git a/content/public/android/junit/src/org/chromium/content/browser/input/TextSuggestionPopupControllerTest.java b/content/public/android/junit/src/org/chromium/content/browser/input/TextSuggestionPopupControllerTest.java index 40e96065..84fa62f4e 100644 --- a/content/public/android/junit/src/org/chromium/content/browser/input/TextSuggestionPopupControllerTest.java +++ b/content/public/android/junit/src/org/chromium/content/browser/input/TextSuggestionPopupControllerTest.java @@ -11,6 +11,7 @@ import static org.mockito.Mockito.when; import android.content.Context; +import android.view.ViewGroup; import androidx.test.core.app.ApplicationProvider; @@ -23,6 +24,7 @@ import org.mockito.junit.MockitoRule; import org.chromium.base.test.BaseRobolectricTestRunner; +import org.chromium.content.browser.RenderCoordinatesImpl; import org.chromium.content.browser.webcontents.WebContentsImpl; import org.chromium.content_public.browser.WebContents.UserDataFactory; import org.chromium.ui.base.ViewAndroidDelegate; @@ -40,15 +42,18 @@ @Mock private WebContentsImpl mWebContents; @Mock private WindowAndroid mWindowAndroid; - @Mock private ViewAndroidDelegate mViewDelegate; + @Mock private RenderCoordinatesImpl mRenderCoordinates; + @Mock private ViewGroup mContainerView; @Before public void setUp() { mContext = ApplicationProvider.getApplicationContext(); + ViewAndroidDelegate viewDelegate = ViewAndroidDelegate.createBasicDelegate(mContainerView); when(mWebContents.getContext()).thenReturn(mContext); when(mWebContents.getTopLevelNativeWindow()).thenReturn(mWindowAndroid); - when(mWebContents.getViewAndroidDelegate()).thenReturn(mViewDelegate); + when(mWebContents.getViewAndroidDelegate()).thenReturn(viewDelegate); + when(mWebContents.getRenderCoordinates()).thenReturn(mRenderCoordinates); when(mWebContents.getOrSetUserData(any(), any())) .thenAnswer( @@ -61,6 +66,19 @@ } @Test + public void testCalculateYOffset() { + when(mRenderCoordinates.getContentOffsetYPix()).thenReturn(50.0f); + when(mContainerView.getHeight()).thenReturn(1000); + + assertEquals(200.0, mController.calculateYOffset(150.0), 0.001); + assertEquals(50.0, mController.calculateYOffset(-100.0), 0.001); + assertEquals(50.0, mController.calculateYOffset(Double.NEGATIVE_INFINITY), 0.001); + assertEquals(50.0, mController.calculateYOffset(Double.NaN), 0.001); + assertEquals(1000.0, mController.calculateYOffset(2000.0), 0.001); + assertEquals(1000.0, mController.calculateYOffset(Double.POSITIVE_INFINITY), 0.001); + } + + @Test public void testDestroyClearsNativePtrsAndHosts() { long nativePtr1 = 111L; long nativePtr2 = 222L;
Original Bug Report
Omnibox overlay spoof via missing top clamp in SuggestionsPopupWindow on Android
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: On Chrome for Android, a compromised renderer can make the browser process draw the native text/spell-check suggestion popup at an arbitrary window Y coordinate — including directly on top of the toolbar/omnibox — because SuggestionsPopupWindow.show() clamps the popup’s Y position only against the bottom screen edge while framework clipping is disabled, and no layer between Mojo ingress and PopupWindow.showAtLocation() validates the renderer-supplied caret coordinates. The popup rows render attacker-chosen strings with omnibox-like styling (gray prefix + black text), so the overlay can display a fake origin such as “https://accounts.google.com” over the real URL while the tab shows attacker content. Demonstrated end-to-end on a real Android device: the popup fully covers the omnibox and can even extend over the system status bar.
Affected files:
content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java:343-377— bottom-only Y clamp, no top clamp;:132disables framework clippingcontent/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.java:201-267— adds top-controls offset, no coordinate validationcontent/browser/android/text_suggestion_host_android.cc:110-176— forwards renderer doubles verbatim to JNI; caps only suggestion countcontent/browser/android/text_suggestion_host_mojo_impl_android.cc:34-50— Mojo impl, forwards all args verbatimcontent/browser/browser_interface_binders.cc:470-475, 1422-1423—blink.mojom.TextSuggestionHostbound for every frame on Android
Estimated timestamp from git blame: 2017-07-26
Summary
blink.mojom.TextSuggestionHost.ShowSpellCheckSuggestionMenu / ShowTextSuggestionMenu (third_party/blink/public/mojom/input/input_host.mojom:24-36) accept unconstrained double caret_x, caret_y and are bound for every RenderFrameHost on Android. The browser-side handlers cap only the suggestion count at 5 and pass the coordinates unmodified through JNI to Java, where the only additions are a renderer-known offset and a bottom-edge clamp. With setClippingEnabled(false) on the popup, WindowManager honors negative and near-zero Y coordinates, and the popup window (TYPE_APPLICATION_PANEL) z-orders above the entire Chrome view hierarchy, toolbar included.
The one gate on the path — HasTransientUserActivation() — does not stop this attacker:
- Any ordinary tap on the attacker page within the preceding 5 s satisfies it (the working exploit sends the IPC from a tap’s
pointeruphandler). - A compromised renderer can also forge it via
LocalFrameHost.UpdateUserActivationState, which the browser accepts with no input-event verification (render_frame_host_impl.cc:9832-9850→frame_tree_node.cc:933-953→ActivateUserActivation,render_frame_host_impl.cc:7750-7760), renewable every 5 s (third_party/blink/public/common/frame/user_activation_state.h:23). content/SECURITY.md:119-124states this explicitly: browser-side user activation is not a security boundary because “the renderer ultimately controls the browser-side user activation state.”
Result: a persistent, re-showable, attacker-styled native overlay on the omnibox surface — the class content/SECURITY.md:107-110 (“UI Spoofing via Unvalidated IPCs”) identifies as valid, with omnibox spoofing called out as the severe end.
Root Cause Analysis
- Unbounded Mojo coordinates, forwarded verbatim:
content/browser/android/text_suggestion_host_android.cc:110-136(spell-check) and:138-176(text-suggestion) enforcekMaxNumberOfSuggestions = 5and the activation gate, then passcaret_x/caret_yunmodified over JNI (:132-135,:172-175). The legitimate Blink caller always sends non-negative viewport-relative caret bounds (third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.cc:455-463), but the browser never enforces that contract. - Java controller adds a renderer-known offset, no validation:
TextSuggestionPopupController.java:224-225/:265-266computecaretYPx + getContentOffsetYPix()and show, checking onlymIsAttachedToWindow(:210-213,:251-254). The offset is the visible top-controls height taken from renderer-authored compositor frame metadata (RenderCoordinatesImpl.java:188-190, set viaGestureListenerManagerImpl.updateScrollInfo), so the attacker knows the exact compensation; precision is not load-bearing anyway, since the popup is 2-4x the toolbar height. - Missing top clamp with framework clipping disabled (the defect):
// SuggestionsPopupWindow.java:132 (createPopupWindow) mPopupWindow.setClippingEnabled(false); // WindowManager will honor off-screen coordsX is clamped on both edges; Y only from below.// SuggestionsPopupWindow.java:343-377 (show) int positionY = (int) Math.round(caretYPx); // renderer-controlled ... positionY += positionInWindow[1]; positionY -= mContentView.getPaddingTop(); positionX = Math.min(menuAtRightEdgeOfWindowPositionX, positionX); // right edge clamped positionX = Math.max(-mContentView.getPaddingLeft(), positionX); // left edge clamped positionY = Math.min(positionY, mDisplayMetrics.heightPixels - height - mContentView.getPaddingTop() - mPopupVerticalMargin); // BOTTOM only — no Math.max mPopupWindow.showAtLocation(mParentView, Gravity.NO_GRAVITY, positionX, positionY);statusBarHeight(:303-308) is used solely to budget the item count (:317-330), never to clamp position. The in-file comments (“shift it up to keep it on-screen”,:367-368) show the code performs its own screen-edge management precisely because clipping is off — the missing top bound is an oversight, not a delegated framework guarantee. - Framework honors the hostile position: AOSP
frameworks/basePopupWindow.java—computeFlagssetsFLAG_LAYOUT_NO_LIMITSwhen clipping is disabled,showAtLocationappliesp.x/p.yverbatim withGravity.NO_GRAVITY≡ TOP|LEFT, and the window type isTYPE_APPLICATION_PANEL, which draws above the host Activity’s view hierarchy including Chrome’s toolbar. Even an on-screen Y in[0, toolbar_height]overlaps the omnibox regardless of clipping, so the overlay depends only on Chromium’s missing clamp. - Attacker-controlled, omnibox-like content:
TextSuggestionsPopupWindow.java:81-107renders each of up to 5 rows as grayprefix+ blacksuggestion+ graysuffix(attacker-supplied strings), mimicking omnibox URL styling;:66reduces the footer to a single “Delete” row. Each new IPC doeshidePopups()+ fresh show (TextSuggestionPopupController.java:215-225), so the overlay is re-established immediately after any dismissal, indefinitely.
Reproduction
Verified end-to-end on a production-configuration ChromePublic build on an Android arm64 device, using MojoJS test bindings as a stand-in for a compromised renderer (the standard threat-model premise for this interface; cf. CVE-2023-6348 on the same binder).
- From the attacker frame, bind
blink.mojom.TextSuggestionHostvia the frame’s BrowserInterfaceBroker (bound per-frame on Android,browser_interface_binders.cc:1422-1423). - Satisfy transient activation: send the IPC from an ordinary tap’s
pointeruphandler (or forge viaLocalFrameHost.UpdateUserActivationState(kNotifyActivation, kInteraction)). - Send
ShowTextSuggestionMenu(caret_x = screen_width/2, caret_y = -120, marked_text = "x", suggestions = [{prefix:"https://", suggestion:"accounts.google.com", suffix:""}, ...]). - Expected: the popup is confined to the content area (positioned at/below the content-area top, as for any legitimate caret).
Observed (screenshot-documented on device):
caret_y=-40: popup top cuts into the omnibox row.caret_y=-120: popup fully covers the omnibox, displaying gray “https://” + black “accounts.google.com” where the real URL was, while the tab renders attacker content.caret_y=-160: popup extends over the system status bar (no top bound at all).caret_y=NaN: also lands at the window top over the omnibox (JavaMath.round(NaN) == 0);+Infinityyields(int) Math.round(+inf) == -1— coordinate sanitization must handle non-finite doubles, not just negatives.- Repeatable indefinitely: after any dismissal (outside tap/back), re-sending the IPC restores the overlay; 5 consecutive re-shows demonstrated.
No crash or sanitizer signal is involved; the demonstrated impact is the spoofed browser UI. A minimal clamp patch at the TextSuggestionPopupController trust boundary was built and verified on-device to restore correct behavior for both -120 and NaN.
Severity
medium — The attacker premise (compromised renderer sending arbitrary Mojo IPC) is explicitly in scope, and content/SECURITY.md:107-110 names compromised-renderer UI spoofing of the omnibox as a valid, severe-end vulnerability class. Per docs/security/severity-guidelines.md this is S2: it is an arbitrary-content spoof on the omnibox surface (outside the S3 carve-outs for “only certain URLs” or non-omnibox surfaces), but not S1 “complete control over the apparent origin” — the overlay retains card edges, elevation shadow, and a footer row, and requires a compromised renderer. Consistent with Chromium precedent rating arbitrary-content Android omnibox spoofs Medium (e.g. CVE-2023-0130, CVE-2023-1231, CVE-2023-2463, CVE-2024-6995). Not a duplicate of CVE-2023-6348 (same entry point, unrelated memory defect, since fixed).
Suggested Fix Direction
Clamp the renderer-supplied caret Y at the trust boundary in TextSuggestionPopupController (both paths): reject/zero non-finite doubles, then lower-bound at 0 before adding getContentOffsetYPix() — clamping later to window top would still allow toolbar overlap, since the container view’s window frame includes the browser-controls region. Because the top-controls offset itself comes from renderer-authored frame metadata, a complete fix should additionally bound the final position against browser-owned controls state; legitimate Blink callers are unaffected either way (they always send finite, non-negative viewport coordinates).
POC: Android omnibox overlay spoofing — renderer-supplied negative caret_y draws the native suggestion popup over the URL bar
Strategy: mojo-js Bug Type: UI spoofing (CWE-451) — browser-process native popup drawn over browser chrome from renderer-controlled coordinates
A compromised renderer calls blink.mojom.TextSuggestionHost.ShowTextSuggestionMenu
(bound for every frame on Android via BindTextSuggestionHostForFrame,
content/browser/browser_interface_binders.cc) with a negative caret_y.
The browser process forwards the doubles verbatim
(content/browser/android/text_suggestion_host_android.cc), the Java controller
adds only the top-controls offset
(TextSuggestionPopupController.java), and SuggestionsPopupWindow.show()
(content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java)
clamps positionY only at the bottom edge (Math.min, lines ~369–375) while the
popup has framework clipping disabled (setClippingEnabled(false), line ~132).
Result: the native, elevation-shadowed suggestion popup — with attacker-chosen,
omnibox-styled rows (gray prefix + black text) — is drawn on top of the Chrome
toolbar/omnibox (and even the system status bar), while the tab displays attacker
content.
This POC uses the MojoJS bindings (--enable-blink-features=MojoJS,MojoJSTest) to
send the exact Mojo message a compromised renderer would send. The only browser-side
gate is transient user activation, satisfied here by an ordinary tap on the attacker
page (the POC sends the IPC from the tap’s pointerup handler); a fully compromised
renderer can also forge activation via LocalFrameHost.UpdateUserActivationState,
which the browser accepts without input-event verification.
Extra finding: non-finite coordinates
Besides negative values, caret_y = NaN also lands the popup over the omnibox
(Java Math.round(NaN) == 0 → window top; see screenshot 5), and +Infinity
would too ((int) Math.round(+inf) == -1). Any fix must sanitize non-finite
doubles, not just clamp negatives — the included bandaid does.
Prerequisites
ChromePublic.apkbuilt from this tree (autoninja -C out/android-arm64 chrome_public_apk).- An arm64 Android device (tested on an Android 17 Cuttlefish VM, 720x1348 @ 280dpi).
- The two generated MojoJS lite bindings in
bindings/(copied unmodified fromout/<dir>/gen/mojo/public/js/mojo_bindings_lite.jsandout/<dir>/gen/third_party/blink/public/mojom/input/input_host.mojom-lite.jsof the same tree).
Reproduction
export ANDROID_SERIAL=<device>
adb install -r -t out/android-arm64/apks/ChromePublic.apk
adb shell 'echo "_ --enable-blink-features=MojoJS,MojoJSTest --disable-fre" > /data/local/tmp/chrome-command-line'
# Serve poc.html + bindings/ on port 8000. Standard setup:
python3 server.py &
adb reverse tcp:8000 tcp:8000
# (In this lab the device could not reach a host-side server — adb reverse
# terminates on a remote adb-server host — so the files were served from the
# device itself instead; identical origin/URL, any HTTP origin works:)
# adb shell mkdir -p /data/local/tmp/poc/bindings
# adb push poc.html device-server.sh /data/local/tmp/poc/
# adb push bindings/*.js /data/local/tmp/poc/bindings/
# adb shell "nohup nc -s 127.0.0.1 -p 8000 -L /system/bin/sh /data/local/tmp/poc/device-server.sh </dev/null >/dev/null 2>&1 &"
adb shell am start -a android.intent.action.VIEW -d "http://localhost:8000/poc.html" \
-n org.chromium.chrome/com.google.android.apps.chrome.Main
sleep 8
# Each tap on the page sends one ShowTextSuggestionMenu with the next caret_y
# from the cycle (-40, -80, -120, -160, ...); a tap while the popup is showing
# dismisses it (the next tap re-triggers — indefinitely repeatable).
adb shell input tap 360 800; sleep 2
adb exec-out screencap -p > result.png
Use poc.html?y=<value> to pin a single caret_y (e.g. ?y=-120, ?y=NaN).
Expected Result
- The page status line reports e.g.
tap #1: sent ShowTextSuggestionMenu(caret_x=360, caret_y=-120). - The native text-suggestion popup appears over the Chrome toolbar/omnibox,
showing the attacker rows
https://accounts.google.com(gray/black omnibox-style spans) and🔒 Connection is secure, hiding the real URL, while the tab renders the attacker page. Atcaret_y=-160the popup also covers part of the system status bar. There is no crash — the impact is the spoofed browser UI.
Output
Observation oracle (UI spoof — no sanitizer involved). Captured screenshots:
screenshot-1-before.png page loaded; real URL "localhost:8000/poc.html"
visible in omnibox; status: "ready … [Mojo OK]"
screenshot-2-caret_y-40-partial.png popup top cuts into the omnibox row
screenshot-3-caret_y-120-omnibox-covered.png popup FULLY covers the omnibox URL; shows
"https://accounts.google.com" where the URL was
screenshot-4-caret_y-160-over-statusbar.png popup extends over the system status bar
(no top bound at all)
screenshot-5-caret_y-NaN-omnibox-covered.png caret_y=NaN also lands at window top,
covering the omnibox
On-page status text visible inside each screenshot documents the exact IPC parameters sent for that frame.
Bandaid Fix
bandaid-fix.patch clamps the renderer-supplied, viewport-relative caret Y at the
trust boundary in TextSuggestionPopupController (both the spell-check and
text-suggestion paths), before the top-controls offset is added:
private static double clampCaretYToViewport(double caretYPx) {
if (!Double.isFinite(caretYPx)) return 0.0;
return Math.max(caretYPx, 0.0);
}
Clamping to >= 0 before adding getContentOffsetYPix() bounds the popup to the
visible content-area top (clamping later, e.g. to window top in
SuggestionsPopupWindow, would still allow toolbar overlap because the container
view’s window frame includes the browser-controls region). Legitimate callers are
unaffected: Blink sends FrameToViewport(AbsoluteCaretBounds()), which is always
finite and non-negative.
Verified on-device (autoninja -C out/android-arm64 chrome_public_apk, reinstall,
same POC):
screenshot-6-fixed-caret_y-120-clamped.png caret_y=-120: omnibox fully visible with the
real URL; popup clamped to content-area top
screenshot-7-fixed-caret_y-NaN-clamped.png caret_y=NaN: same clamped placement
Residual (out of scope for this bandaid): the top-controls offset itself comes from
renderer-authored compositor frame metadata, so a renderer claiming offset 0 while
controls are shown could still shift placement by the toolbar height; a complete fix
should bound the final positionY against browser-owned controls state.
Files
poc.html— the attacker page (MojoJS caller);?y=/?x=pin coordinatesserver.py— standard host-side HTTP serverdevice-server.sh— on-device toybox-nc HTTP handler (lab fallback, see above)bindings/— unmodified generated MojoJS lite bindings (reproduction dependency)bandaid-fix.patch— minimal fix, verified abovescreenshot-*.png— proof (before / vulnerable / fixed)
Attached file hierarchy:
The following directories were attached as zip archives:
bindings.zip→bindings/
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.
- http://localhost:8000/poc.html
- https://accounts.google.com
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/android/text_suggestion_host_android.cc
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/android/text_suggestion_host_android.cc;l=110
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/android/text_suggestion_host_mojo_impl_android.cc;l=34
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_interface_binders.cc
- https://source.chromium.org/chromium/chromium/src/+/main:content/browser/browser_interface_binders.cc;l=470
- https://source.chromium.org/chromium/chromium/src/+/main:content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java
- https://source.chromium.org/chromium/chromium/src/+/main:content/public/android/java/src/org/chromium/content/browser/input/SuggestionsPopupWindow.java;l=343
- https://source.chromium.org/chromium/chromium/src/+/main:content/public/android/java/src/org/chromium/content/browser/input/TextSuggestionPopupController.java;l=201
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/common/frame/user_activation_state.h;l=23
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/public/mojom/input/input_host.mojom;l=24
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/editing/suggestion/text_suggestion_controller.cc;l=455