Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Omnibox
DescriptionIncorrect security UI in Omnibox
ComponentOmnibox
Bug ClassLogic Error
Tracker484082189
Fix commiteaf32e20c880 (chromium/src) +239/-4
CISA KEVNot listed
Creditedmohamedhesham9173
Disclosed2026-04-07

Files Changed

  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
  • chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java
From eaf32e20c8801aa6cc4494186fd4b2b2580aaa02 Mon Sep 17 00:00:00 2001
From: Patrick Noland <pnoland@google.com>
Date: Thu, 26 Feb 2026 17:02:44 -0800
Subject: [PATCH] [mobar] Fix RTL mobar animation

The starting position of the location bar is different for RTL, which
we need to account for when calculating the final translationX.

Bug: 484082189
Change-Id: I3ed5c3954c0e4c7a290936521ad2a101c42b7f14
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7610025
Reviewed-by: Sky Malice <skym@chromium.org>
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1591207}
---

diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
index 2e33b82..948ed009 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarController.java
@@ -32,6 +32,7 @@
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
 import org.chromium.ui.KeyboardVisibilityDelegate;
 import org.chromium.ui.KeyboardVisibilityDelegate.KeyboardVisibilityListener;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.base.ViewUtils;
 import org.chromium.ui.insets.InsetObserver;
 import org.chromium.ui.insets.InsetObserver.WindowInsetsAnimationListener;
@@ -127,7 +128,7 @@
     private float mStartingLocationBarX;
     // The final horizontal position of the location bar when the mini origin bar is in its
     // fully-minimized state.
-    private float mFinalLocationBarX;
+    private float mFinalLocationBarTranslationX;
     private boolean mShowingMiniOriginBar;
 
     /**
@@ -309,9 +310,17 @@
         locationBarView.measure(
                 MeasureSpec.makeMeasureSpec(controlContainerWidth, MeasureSpec.AT_MOST),
                 MeasureSpec.makeMeasureSpec(newLocationBarHeight, MeasureSpec.AT_MOST));
-        mStartingLocationBarX = mDefaultLocationBarLayoutParams.leftMargin;
+
+        boolean isRtl = LocalizationUtils.isLayoutRtl();
+        int viewWidth = locationBarView.getMeasuredWidth();
+        // The "resting position" of the left edge of the location bar assuming no translation.
+        float baseLayoutLeftX = isRtl ? controlContainerWidth - viewWidth : 0;
+
+        mStartingLocationBarX = mDefaultLocationBarLayoutParams.leftMargin - baseLayoutLeftX;
         float finalLocationBarWidth = locationBarView.getMeasuredWidth() * LOCATION_BAR_FINAL_SCALE;
-        mFinalLocationBarX = (controlContainerWidth - finalLocationBarWidth) / 2;
+        // The final x coordinate of the left edge that centers it horizontally.
+        float targetAbsoluteLeftX = (controlContainerWidth - finalLocationBarWidth) / 2f;
+        mFinalLocationBarTranslationX = targetAbsoluteLeftX - baseLayoutLeftX;
     }
 
     private void hideMiniOriginBar() {
@@ -460,7 +469,8 @@
     private void setMinimizationProgress(float minimizationProgress) {
         float translationX =
                 mStartingLocationBarX
-                        + minimizationProgress * (mFinalLocationBarX - mStartingLocationBarX);
+                        + minimizationProgress
+                                * (mFinalLocationBarTranslationX - mStartingLocationBarX);
         mLocationBar.getContainerView().setTranslationX(translationX);
 
         float scale = 1.0f - minimizationProgress / LOCATION_BAR_SCALE_DENOMINATOR;
diff --git a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java
index e0c99fe..a4ab3b8 100644
--- a/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java
+++ b/chrome/browser/ui/android/toolbar/java/src/org/chromium/chrome/browser/toolbar/MiniOriginBarControllerTest.java
@@ -53,6 +53,7 @@
 import org.chromium.components.browser_ui.widget.TouchEventObserver;
 import org.chromium.content.browser.input.ImeAdapterImpl;
 import org.chromium.content.browser.webcontents.WebContentsImpl;
+import org.chromium.ui.base.LocalizationUtils;
 import org.chromium.ui.insets.InsetObserver;
 
 import java.util.Collections;
@@ -467,6 +468,230 @@
     }
 
     @Test
+    public void testAnimateWithKeyboard_RTL() {
+        LocalizationUtils.setRtlForTesting(true);
+        try {
+            doReturn(ControlsPosition.BOTTOM).when(mBrowserControlsSizer).getControlsPosition();
+            mMiniOriginBarController.onControlsPositionChanged(ControlsPosition.BOTTOM);
+
+            final MiniOriginWindowInsetsAnimationListener animationListener =
+                    mMiniOriginBarController.getAnimationListenerForTesting();
+            final int finalKeyboardHeight = 100;
+            final BoundsCompat bounds =
+                    new BoundsCompat(Insets.NONE, Insets.of(0, 0, 0, finalKeyboardHeight));
+
+            mIsFormFieldFocused.onNodeAttributeUpdated(true, false);
+
+            final int locationBarStartPosition = 50; // leftMargin
+            final int rightMargin = 50;
+            mLocationBarLayoutParams.leftMargin = locationBarStartPosition;
+            mLocationBarLayoutParams.rightMargin = rightMargin;
+
+            final int locationBarMiniWidth = 100;
+            doReturn(locationBarMiniWidth).when(mLocationBarView).getMeasuredWidth();
+
+            float finalLocationBarWidth =
+                    locationBarMiniWidth * MiniOriginBarController.LOCATION_BAR_FINAL_SCALE;
+
+            // Calculate RTL specific coordinates and translations
+            float baseLayoutLeftX = CONTROL_CONTAINER_WIDTH - locationBarMiniWidth;
+            float targetAbsoluteLeftX = (CONTROL_CONTAINER_WIDTH - finalLocationBarWidth) / 2f;
+
+            final float startX = locationBarStartPosition - baseLayoutLeftX;
+            final float finalTranslationX = targetAbsoluteLeftX - baseLayoutLeftX;
+            final float positionDelta = finalTranslationX - startX;
+
+            // The url bar height is smaller than the total height of the mobar due to vertical
+            // margin.
+            final float urlBarHeight =
+                    mContext.getResources().getDimensionPixelSize(R.dimen.mini_origin_bar_height)
+                            - 6;
+            doReturn(urlBarHeight).when(mLocationBar).getUrlBarHeight();
+
+            animationListener.onPrepare(mImeAnimation);
+            mKeyboardVisibilityDelegate.setVisibilityForTests(true);
+            animationListener.onStart(mImeAnimation, bounds);
+            Assert.assertEquals(
+                    MiniOriginState.ANIMATING,
+                    mMiniOriginBarController.getCurrentStateForTesting());
+
+            int currentKeyboardHeight = 10;
+            final int systemBarsHeight = 13;
+            WindowInsetsCompat.Builder insetsBuilder =
+                    new WindowInsetsCompat.Builder()
+                            .setInsets(
+                                    WindowInsetsCompat.Type.ime(),
+                                    Insets.of(0, 0, 0, currentKeyboardHeight))
+                            .setInsets(
+                                    WindowInsetsCompat.Type.systemBars(),
+                                    Insets.of(0, 0, 0, systemBarsHeight));
+            WindowInsetsCompat insets = insetsBuilder.build();
+
+            // --- PROGRESS 0.1 ---
+            mImeAnimation.setFraction(0.1f);
+            animationListener.onProgress(insets, Collections.singletonList(mImeAnimation));
+            assertEquals(
+                    finalKeyboardHeight - currentKeyboardHeight,
+                    (int) mControlContainerTranslationSupplier.get());
+            verify(mLocationBarView)
+                    .setTranslationX(startX + mImeAnimation.getFraction() * positionDelta);
+            verify(mLocationBarView)
+                    .setScaleX(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+            verify(mLocationBarView)
+                    .setScaleY(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+            verify(mLocationBarView).setPivotY(urlBarHeight / 2);
+
+            // --- PROGRESS 0.4 ---
+            currentKeyboardHeight = 40;
+            insets =
+                    insetsBuilder
+                            .setInsets(
+                                    WindowInsetsCompat.Type.ime(),
+                                    Insets.of(0, 0, 0, currentKeyboardHeight))
+                            .build();
+            mImeAnimation.setFraction(0.4f);
+            animationListener.onProgress(insets, Collections.singletonList(mImeAnimation));
+            assertEquals(
+                    finalKeyboardHeight - currentKeyboardHeight,
+                    (int) mControlContainerTranslationSupplier.get());
+            verify(mLocationBarView)
+                    .setTranslationX(startX + mImeAnimation.getFraction() * positionDelta);
+            verify(mLocationBarView)
+                    .setScaleX(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+            verify(mLocationBarView)
+                    .setScaleY(
+                            1.0f
+                                    - mImeAnimation.getFraction()
+                                            / MiniOriginBarController
+                                                    .LOCATION_BAR_SCALE_DENOMINATOR);
+
+            // --- PROGRESS 0.9 ---
+            currentKeyboardHeight = 90;
+            insets =
+                    insetsBuilder
+                            .setInsets(
+                                    WindowInsetsCompat.Type.ime(),
+                                    Insets.of(0, 0, 0, currentKeyboardHeight))
+                            .build();
Loading diff…

Original Bug Report

reported by mo...@gmail.com

Security Regression: Trusted UI (Omnibox) fails to render/disappears due to CoordinatorLayout migration logic failure.

  1. Executive Summary

A rendering regression was identified in Chromium for Android (arm64), causing the URL Bar (Omnibox) to disappear or render incorrectly when positioned at the bottom of the screen. The issue was introduced in Revision 1501003 during a structural migration of the toolbar container from OptimizedFrameLayout to CoordinatorLayout. This change conflicts with high-DPI resource handling on specific devices (e.g., Realme 12), leading to layout inflation errors and breaking the Trusted UI boundary.

  1. Issue Description

Affected Version: Chromium Builds starting from Revision 1501003.

Device Specifics: Verified on Realme 12 (Android 15), likely affects other high-DPI devices.

Trigger Condition: The issue manifests specifically when the “Bottom Toolbar” setting is enabled (or forced via flags), moving the URL bar to the bottom of the screen.

  1. Bisect & Regression Range

Using manual binary search (bisect) on Android_Arm64 snapshots, the regression was isolated to a specific range:

Last Known Good Revision: 1500987

First Bad Revision: 1501016

Culprit Commit Position: refs/heads/main@{#1501003}

  1. Identification of the Culprit

Commit Hash: e981dd46821f563acaa6a5a3d8fd51e046e311e2.

Title: [Toolbar] Rework hairline positioning.

Change Log: “Rework hairline positioning… This does require switching the toolbar container to be a CoordinatorLayout… Instead of manual manipulation.”

  1. Technical Root Cause Analysis

The root cause is a logic failure in how the new CoordinatorLayout handles the toolbar’s vertical positioning compared to the previous FrameLayout implementation, specifically when interacting with high-density display resources.

A. Structural Change The culprit commit replaced the root container of the toolbar in control_container.xml:

Removed: org.chromium.components.browser_ui.widget.ViewResourceFrameLayout

Added: androidx.coordinatorlayout.widget.CoordinatorLayout

Additional Evidence in ToolbarControlContainer.java: The file now explicitly casts layout params to CoordinatorLayout.LayoutParams in mutateLayoutParams(), confirming that the container strictly enforces the new layout hierarchy which fails to handle the Hi-DPI scaling correctly.

B. Logic Regression in ToolbarPositionController.java Upon inspecting the source code of ToolbarPositionController.java, specifically the updateCurrentPosition() method:

Removal of Explicit Offsets: The legacy code used to manually calculate and set the Y-translation (setTranslationY) based on screen height and offsets. This guaranteed the view was placed within visible bounds.

Reliance on Gravity: The new implementation relies entirely on CoordinatorLayout.LayoutParams and gravity anchors: // Code Snippet from ToolbarPositionController.java (New Logic) CoordinatorLayout.LayoutParams hairlineLayoutParams = mControlContainer.mutateHairlineLayoutParams();

// Logic relies solely on flipping gravity hairlineLayoutParams.anchorGravity = newControlsPosition == ControlsPosition.TOP ? Gravity.BOTTOM : Gravity.TOP;

LayoutParams layoutParams = mControlContainer.mutateLayoutParams(); int verticalGravity = newControlsPosition == ControlsPosition.TOP ? Gravity.TOP : Gravity.BOTTOM;

layoutParams.gravity = Gravity.START | verticalGravity;

C. The Conflict The reliance on Gravity.BOTTOM failed because of a preceding commit 1500950 (Enable hi-dpi resources).

The new Hi-DPI assets likely altered the measured height of the toolbar children.

CoordinatorLayout requires precise measurement passes (onMeasure). If the inner views (Omnibox icons) have mismatched dimensions due to the new resources, the CoordinatorLayout may calculate a zero height or push the view off-screen, whereas the old FrameLayout would have forced it to render regardless of measurement errors.

Security Impact Analysis: According to Chrome’s security guidelines, a UI spoof is critical if it “convinces the user they are currently on origin A when in fact they are on origin B”.

This regression (caused by CoordinatorLayout migration) actively corrupts the primary ‘Security Cue’ (the Origin/URL text) that a “reasonable and prudent user” relies on.

Specifically, the failure in CoordinatorLayout to handle Hi-DPI measurements causes the domain text view to be shifted off-screen or rendered with incorrect padding, effectively truncating the origin or leaving the URL bar blank during keyboard interaction. This renders the domain unreadable, denying the user the ability to verify the origin and directly facilitating the criteria mentioned in your guidelines.

Unlike a design choice, this is a code failure in ToolbarPositionController.java that breaks the trusted UI boundary involuntarily.

  1. Reproduction Steps

Install Chromium Android_Arm64 build 1501016 (or any build after 1501003).

Launch the browser.

Navigate to chrome://flags or Settings.

Enable “Bottom Toolbar” (or Android Bottom Toolbar flag).

Relaunch the browser and open any webpage.

Tap on an input field to trigger the virtual keyboard.

Observation: Upon triggering the virtual keyboard, the bottom-anchored Omnibox layout fails, causing the domain origin text to be either shifted off-screen, or rendered with zero height, making the origin unreadable.

  1. Proposed Fix / Recommendation

To resolve this regression, the CoordinatorLayout implementation in ToolbarPositionController.java must account for the safe area insets and potential measurement discrepancies caused by Hi-DPI assets.

Immediate Mitigation: Revert commit e981dd4 to restore OptimizedFrameLayout stability.

Long-term Fix: Modify updateCurrentPosition() to ensure a minimum valid height is enforced and that layoutParams.bottomMargin correctly accounts for the device’s navigation bar/gesture area when Gravity.BOTTOM is applied.

View on issue tracker