Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI
DescriptionIncorrect security UI
ComponentChromium
Bug ClassLogic Error
Tracker467448811
Fix commitd9cb337ff4a7 (chromium/src) +68/-44
CISA KEVNot listed
CreditedKhalil Zhani
Disclosed2026-01-13

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java
  • chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManagerUnitTest.java
From d9cb337ff4a744969259fa8cac73512f4f5fe2a4 Mon Sep 17 00:00:00 2001
From: Patrick Noland <pnoland@google.com>
Date: Thu, 11 Dec 2025 15:20:05 -0800
Subject: [PATCH] Force relayout for tab-driven constraint changes

This logic currently only runs for browserdelegate-driven constraint
changes, which misses e.g. form-field focus driven SHOWN.

Bug: 467448811
Change-Id: I5662353216c8faea84a98e2f3edfa584cd6adf2c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7253201
Reviewed-by: Wenyu Fu <wenyufu@chromium.org>
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1557729}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java
index 1f6a93c..9a78efa6 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManager.java
@@ -222,47 +222,7 @@
         mBrowserVisibilityDelegate =
                 new BrowserStateBrowserControlsVisibilityDelegate(
                         mHtmlApiHandler.getPersistentFullscreenModeSupplier());
-        mBrowserVisibilityDelegate.addObserver(
-                (constraints) -> {
-                    if (constraints == BrowserControlsState.SHOWN) {
-                        // When compositor can drive the animation to show controls, do not call
-                        // setPositionsForTabToNonFullscreen to avoid control offset being forced
-                        // set to 0 before the render-driven animation kicks in.
-                        boolean allowRenderDrivenShowConstraint =
-                                ChromeFeatureList.sBrowserControlsRenderDrivenShowConstraint
-                                        .isEnabled();
-                        boolean renderDrivenShowConstraint =
-                                allowRenderDrivenShowConstraint
-                                        && canAnimateNativeBrowserControls();
-                        if (!renderDrivenShowConstraint) {
-                            setPositionsForTabToNonFullscreen();
-                        }
-
-                        // TODO(https://crbug.com/449011189): Maybe cleanup
-                        if (allowRenderDrivenShowConstraint) {
-                            RecordHistogram.recordBooleanHistogram(
-                                    "Android.BrowserControls.RenderDrivenShowConstraint",
-                                    renderDrivenShowConstraint);
-                        }
-
-                        // If controls become locked, it's possible we've previously delayed
-                        // actually setting visibility until a touch event is over. In this case, we
-                        // need to trigger an update again now, which should go through due to
-                        // constraints.
-                        scheduleVisibilityUpdate();
-                    }
-
-                    // From https://crbug.com/452885338, https://crbug.com/461532432: When changing
-                    // controls visibility when exiting fullscreen, the visibility change might not
-                    // honor a redraw. We do this through forcing a relayout to avoid the toolbar
-                    // remains hidden.
-                    if ((constraints == BrowserControlsState.SHOWN
-                                    || constraints == BrowserControlsState.BOTH)
-                            && getAndroidControlsVisibility() != View.VISIBLE) {
-                        mForceRelayoutOnVisibilityChange = true;
-                        scheduleVisibilityUpdate();
-                    }
-                });
+        mBrowserVisibilityDelegate.addObserver(this::onConstraintsChanged);
     }
 
     /**
@@ -354,9 +314,10 @@
                                 new OffsetTagConstraints(
                                         0, 0, -(mTopControlsHeight + hairlineHeight), 0);
 
+                        onConstraintsChanged(constraints);
                         // Notify observers of changes before passing tags to native so observers
                         // can set their relevant fields in offsetTagsInfo.
-                        notifyConstraintsChanged(oldOffsetTagsInfo, offsetTagsInfo, constraints);
+                        notifyOffsetTagsChanged(oldOffsetTagsInfo, offsetTagsInfo, constraints);
 
                         offsetTagsInfo
                                 .getConstraints()
@@ -850,7 +811,9 @@
             return;
         }
         final int desiredVisibility = shouldShowAndroidControls() ? View.VISIBLE : View.INVISIBLE;
-        if (mControlContainer.getView().getVisibility() == desiredVisibility) return;
+        if (mControlContainer.getView().getVisibility() == desiredVisibility) {
+            return;
+        }
         mControlContainer.getView().removeCallbacks(mUpdateVisibilityRunnable);
         mControlContainer.getView().postOnAnimation(mUpdateVisibilityRunnable);
     }
@@ -1022,7 +985,45 @@
         }
     }
 
-    private void notifyConstraintsChanged(
+    private void onConstraintsChanged(@BrowserControlsState int constraints) {
+        if (constraints == BrowserControlsState.SHOWN) {
+            // When compositor can drive the animation to show controls, do not call
+            // setPositionsForTabToNonFullscreen to avoid control offset being forced
+            // set to 0 before the render-driven animation kicks in.
+            boolean allowRenderDrivenShowConstraint =
+                    ChromeFeatureList.sBrowserControlsRenderDrivenShowConstraint.isEnabled();
+            boolean renderDrivenShowConstraint =
+                    allowRenderDrivenShowConstraint && canAnimateNativeBrowserControls();
+            if (!renderDrivenShowConstraint) {
+                setPositionsForTabToNonFullscreen();
+            }
+
+            // TODO(https://crbug.com/449011189): Maybe cleanup
+            if (allowRenderDrivenShowConstraint) {
+                RecordHistogram.recordBooleanHistogram(
+                        "Android.BrowserControls.RenderDrivenShowConstraint",
+                        renderDrivenShowConstraint);
+            }
+
+            // If controls become locked, it's possible we've previously delayed
+            // actually setting visibility until a touch event is over. In this case, we
+            // need to trigger an update again now, which should go through due to
+            // constraints.
+            scheduleVisibilityUpdate();
+        }
+
+        // From https://crbug.com/452885338, https://crbug.com/461532432: When changing
+        // controls visibility when exiting fullscreen, the visibility change might not
+        // honor a redraw. We do this through forcing a relayout to avoid the toolbar
+        // remains hidden.
+        if ((constraints == BrowserControlsState.SHOWN || constraints == BrowserControlsState.BOTH)
+                && getAndroidControlsVisibility() != View.VISIBLE) {
+            mForceRelayoutOnVisibilityChange = true;
+            scheduleVisibilityUpdate();
+        }
+    }
+
+    private void notifyOffsetTagsChanged(
             BrowserControlsOffsetTagsInfo oldOffsetTagsInfo,
             BrowserControlsOffsetTagsInfo offsetTagsInfo,
             @BrowserControlsState int constraints) {
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManagerUnitTest.java
index 60399a7..e1d8392 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManagerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/fullscreen/BrowserControlsManagerUnitTest.java
@@ -54,6 +54,7 @@
 import org.chromium.cc.input.BrowserControlsState;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityTabProvider;
+import org.chromium.chrome.browser.browser_controls.BrowserControlsOffsetTagsInfo;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
 import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider.ControlsPosition;
 import org.chromium.chrome.browser.browser_controls.BrowserStateBrowserControlsVisibilityDelegate;
@@ -784,6 +785,28 @@
                         anyBoolean());
     }
 
+    @Test
+    public void testConstraintChangeFromTab() {
+        remakeWithoutSpy();
+        notifyAddTab(mTab);
+        mActivityTabProvider.setForTesting(mTab);
+        // Put the control container in a hidden state and bottom-positioned.
+        mBrowserControlsManager.setControlsPosition(
+                ControlsPosition.BOTTOM, 0, 0, 0, TOOLBAR_HEIGHT, 10, TOOLBAR_HEIGHT);
+        ShadowLooper.idleMainLooper();
+        Mockito.clearInvocations(mContainerView);
+        // Locking the controls via the TabControlsObserver should check for forced relayout.
+        mBrowserControlsManager
+                .getTabControlsObserverForTesting()
+                .onOffsetTagsInfoChanged(
+                        mTab,
+                        new BrowserControlsOffsetTagsInfo(),
+                        new BrowserControlsOffsetTagsInfo(),
+                        BrowserControlsState.SHOWN);
+        ShadowLooper.idleMainLooper();
+        verify(mContainerView).requestLayout();
+    }
+
     private void verifyUpdateOffsetTagDefinitions(
             OffsetTagConstraints top, OffsetTagConstraints content, OffsetTagConstraints bottom) {
         BrowserControlsOffsetTagConstraints expectedConstraints =
Loading diff…

Original Bug Report

reported by ch...@gmail.com

Mini bar not rendered when omnibox is hidden (similar to issue 461532432)

Steps to reproduce the problem

  1. Open the testcase.
  2. Scroll down to the middle of the page until the omnibox disappears (normal fullscreen scroll behavior).
  3. Tap inside the <textarea>.

Problem Description

This issue appears very similar to Chromium issue 461532432.

In Chrome Canary on Android, when the omnibox auto-hides during scroll, tapping inside a <textarea> causes the keyboard’s accessory bar (the mini bar above the virtual keyboard) to not render at all. The space where the accessory bar normally appears is still allocated, but it is completely empty (no icons, no background, just blank UI).

This empty UI zone appears exactly where a user expects browser controls. A website can use CSS to place a fake omnibox just below it, enabling UI spoofing.

Summary

Mini bar not rendered when omnibox is hidden (similar to issue 461532432)

Additional Data

Category: Security
Chrome Channel: Canary
Regression: N/A \

View on issue tracker