0

[Offline indicator v2] Fix blink layout height and fullscreen

Today, most parts of the code assume the browser controls min-height
is 0. This causes issues with the layout size and the fullscreen mode.
The most obvious issues currently are:
- If a min-height is set, ChromeFullscreenManager#controlsResizeView()
always returns true since the controls aren't completely hidden.
- Similarly if a min-height is set, position: fixed elements are sized
as if the controls are fully showing.

This CL changes the "browser controls resize/shrink blink" logic to try
and fix the issues listed above. With the changes in this CL, the
controlsResizeView bit will be false if the controls are at the
min-height.

Bug: 1049301
Change-Id: Ieb95e92c983b56b42a640f726c60c2eec9964064
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2048276
Commit-Queue: Sinan Sahin <sinansahin@google.com>
Reviewed-by: Theresa  <twellington@chromium.org>
Reviewed-by: Matthew Jones <mdjones@chromium.org>
Reviewed-by: David Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#750260}
This commit is contained in:
Sinan Sahin
2020-03-13 19:19:13 +00:00
committed by Commit Bot
parent aa010ad347
commit 122f7ac1a7
10 changed files with 469 additions and 25 deletions
cc/trees
chrome/android
chrome_junit_test_java_sources.gni
java
src
org
chromium
junit
src
org
chromium
chrome
third_party/blink/renderer/core

@ -43,11 +43,11 @@ struct ApplyViewportChangesArgs {
bool is_pinch_gesture_active;
// How much the top controls have been shown or hidden. The ratio runs
// between 0 (hidden) and 1 (full-shown). This is additive.
// between a set min-height (default 0) and 1 (full-shown). This is additive.
float top_controls_delta;
// How much the bottom controls have been shown or hidden. The ratio runs
// between 0 (hidden) and 1 (full-shown). This is additive.
// between a set min-height (default 0) and 1 (full-shown). This is additive.
float bottom_controls_delta;
// Whether the browser controls have been locked to fully hidden or shown or

@ -369,21 +369,22 @@ void LayerTreeImpl::UpdateViewportContainerSizes() {
ViewportAnchor anchor(InnerViewportScrollNode(), OuterViewportScrollNode(),
this);
// Top/bottom controls always share the same shown ratio.
float top_controls_shown_ratio =
top_controls_shown_ratio_->Current(IsActiveTree());
float bottom_controls_shown_ratio =
bottom_controls_shown_ratio_->Current(IsActiveTree());
float top_controls_layout_height =
browser_controls_shrink_blink_size() ? top_controls_height() : 0.f;
float top_controls_layout_height = browser_controls_shrink_blink_size()
? top_controls_height()
: top_controls_min_height();
float top_content_offset =
top_controls_height() > 0
? top_controls_height() * top_controls_shown_ratio
: 0.f;
float delta_from_top_controls =
top_controls_layout_height - top_content_offset;
float bottom_controls_layout_height =
browser_controls_shrink_blink_size() ? bottom_controls_height() : 0.f;
float bottom_controls_layout_height = browser_controls_shrink_blink_size()
? bottom_controls_height()
: bottom_controls_min_height();
float bottom_content_offset =
bottom_controls_height() > 0
? bottom_controls_height() * bottom_controls_shown_ratio

@ -79,6 +79,7 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/firstrun/FirstRunIntegrationUnitTest.java",
"junit/src/org/chromium/chrome/browser/firstrun/ToSAckedReceiverTest.java",
"junit/src/org/chromium/chrome/browser/fullscreen/BrowserStateBrowserControlsVisibilityDelegateTest.java",
"junit/src/org/chromium/chrome/browser/fullscreen/FullscreenManagerUnitTest.java",
"junit/src/org/chromium/chrome/browser/gcore/GoogleApiClientHelperTest.java",
"junit/src/org/chromium/chrome/browser/gsa/GSAStateUnitTest.java",
"junit/src/org/chromium/chrome/browser/history/HistoryAdapterAccessibilityTest.java",

@ -575,6 +575,9 @@ public class CompositorViewHolder extends FrameLayout
for (TouchEventObserver o : mTouchEventObservers) {
if (o.shouldInterceptTouchEvent(e)) return true;
}
if (mFullscreenManager != null) mFullscreenManager.onMotionEvent(e);
if (mLayoutManager == null) return false;
mEventOffsetHandler.onInterceptTouchEvent(e);
@ -708,9 +711,13 @@ public class CompositorViewHolder extends FrameLayout
// The view size takes into account of the browser controls whose height
// should be subtracted from the view if they are visible, therefore shrink
// Blink-side view size.
final int totalMinHeight = mFullscreenManager != null
? mFullscreenManager.getTopControlsMinHeight()
+ mFullscreenManager.getBottomControlsMinHeight()
: 0;
int controlsHeight = controlsResizeView()
? getTopControlsHeightPixels() + getBottomControlsHeightPixels()
: 0;
: totalMinHeight;
if (isAttachedToWindow(view)) {
webContents.setSize(w, h - controlsHeight);
@ -989,7 +996,7 @@ public class CompositorViewHolder extends FrameLayout
* @return {@code true} if browser controls shrink Blink view's size.
*/
public boolean controlsResizeView() {
return mFullscreenManager != null ? mFullscreenManager.controlsResizeView() : false;
return mFullscreenManager != null && mFullscreenManager.controlsResizeView();
}
@Override

@ -17,6 +17,7 @@ import android.widget.FrameLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
@ -471,7 +472,7 @@ public class ChromeFullscreenManager extends FullscreenManager
@Override
public void onEnterFullscreen(FullscreenOptions options) {
Tab tab = getTab();
if (areBrowserControlsOffScreen()) {
if (areBrowserControlsAtMinHeight()) {
// The browser controls are currently hidden.
getHtmlApiHandler().enterFullscreen(tab, options);
} else {
@ -518,6 +519,15 @@ public class ChromeFullscreenManager extends FullscreenManager
return getBrowserControlHiddenRatio() == 1.0f;
}
/**
* @return True if the browser controls are showing as much as the min height. Note that this is
* the same as {@link #areBrowserControlsOffScreen()} when both min-heights are 0.
*/
public boolean areBrowserControlsAtMinHeight() {
return getContentOffset() == getTopControlsMinHeight()
&& getBottomContentOffset() == getBottomControlsMinHeight();
}
/**
* @return True if the browser controls are currently completely visible.
*/
@ -617,6 +627,14 @@ public class ChromeFullscreenManager extends FullscreenManager
return mRendererTopControlsMinHeightOffset;
}
/**
* @return The content offset from the bottom of the screen, or the visible height of the bottom
* controls, in px.
*/
public int getBottomContentOffset() {
return getBottomControlsHeight() - getBottomControlOffset();
}
@Override
public int getBottomControlOffset() {
// If the height is currently 0, the offset generated by the bottom controls should be too.
@ -678,19 +696,25 @@ public class ChromeFullscreenManager extends FullscreenManager
public void updateViewportSize() {
if (mInGesture || mContentViewScrolling) return;
// Update content viewport size only when the browser controls are not animating.
int topContentOffset = mRendererTopContentOffset;
int bottomControlOffset = mRendererBottomControlOffset;
if ((topContentOffset != 0 && topContentOffset != getTopControlsHeight())
&& bottomControlOffset != 0 && bottomControlOffset != getBottomControlsHeight()) {
return;
}
boolean controlsResizeView =
topContentOffset > 0 || bottomControlOffset < getBottomControlsHeight();
mControlsResizeView = controlsResizeView;
// Update content viewport size only if the browser controls are not moving, i.e. not
// scrolling or animating.
if (!areBrowserControlsIdle()) return;
mControlsResizeView = getContentOffset() > getTopControlsMinHeight()
|| getBottomContentOffset() > getBottomControlsMinHeight();
for (FullscreenListener listener : mListeners) listener.onUpdateViewportSize();
}
/**
* @return Whether browser controls are currently idle, i.e. not scrolling or animating.
*/
private boolean areBrowserControlsIdle() {
return (getContentOffset() == getTopControlsMinHeight()
|| getContentOffset() == getTopControlsHeight())
&& (getBottomContentOffset() == getBottomControlsMinHeight()
|| getBottomContentOffset() == getBottomControlsHeight());
}
// View.OnHierarchyChangeListener implementation
@Override
@ -709,7 +733,7 @@ public class ChromeFullscreenManager extends FullscreenManager
if (view == null) return;
float topViewsTranslation = getTopVisibleContentOffset();
float bottomMargin = getBottomControlsHeight() - getBottomControlOffset();
float bottomMargin = getBottomContentOffset();
applyTranslationToTopChildViews(view, topViewsTranslation);
applyMarginToFullChildViews(view, topViewsTranslation, bottomMargin);
updateViewportSize();
@ -757,7 +781,7 @@ public class ChromeFullscreenManager extends FullscreenManager
}
final Tab tab = getTab();
if (tab != null && areBrowserControlsOffScreen() && mIsEnteringPersistentModeState) {
if (tab != null && areBrowserControlsAtMinHeight() && mIsEnteringPersistentModeState) {
getHtmlApiHandler().enterFullscreen(tab, mPendingFullscreenOptions);
mIsEnteringPersistentModeState = false;
mPendingFullscreenOptions = null;
@ -1136,4 +1160,9 @@ public class ChromeFullscreenManager extends FullscreenManager
}
VrModuleProvider.unregisterVrModeObserver(this);
}
@VisibleForTesting
TabModelSelectorTabObserver getTabFullscreenObserverForTesting() {
return mTabFullscreenObserver;
}
}

@ -0,0 +1,269 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.fullscreen;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Activity;
import android.view.View;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.UserDataHost;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabObserver;
import org.chromium.chrome.browser.toolbar.ControlContainer;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.components.embedder_support.view.ContentView;
/**
* Unit tests for {@link ChromeFullscreenManager}.
*/
@RunWith(BaseRobolectricTestRunner.class)
public class FullscreenManagerUnitTest {
@Rule
public TestRule mProcessor = new Features.JUnitProcessor();
// Since these tests don't depend on the heights being pixels, we can use these as dpi directly.
private static final int TOOLBAR_HEIGHT = 56;
private static final int EXTRA_TOP_CONTROL_HEIGHT = 20;
@Mock
private Activity mActivity;
@Mock
private ControlContainer mControlContainer;
@Mock
private View mContainerView;
@Mock
private TabModelSelector mTabModelSelector;
@Mock
private android.content.res.Resources mResources;
@Mock
private ChromeFullscreenManager.FullscreenListener mFullscreenListener;
@Mock
private Tab mTab;
@Mock
private ContentView mContentView;
private UserDataHost mUserDataHost = new UserDataHost();
private ChromeFullscreenManager mFullscreenManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.CREATED);
when(mActivity.getResources()).thenReturn(mResources);
when(mResources.getDimensionPixelSize(R.dimen.control_container_height))
.thenReturn(TOOLBAR_HEIGHT);
when(mControlContainer.getView()).thenReturn(mContainerView);
when(mContainerView.getVisibility()).thenReturn(View.VISIBLE);
when(mTab.isUserInteractable()).thenReturn(true);
when(mTab.isInitialized()).thenReturn(true);
when(mTab.getUserDataHost()).thenReturn(mUserDataHost);
when(mTab.getContentView()).thenReturn(mContentView);
doNothing().when(mContentView).removeOnHierarchyChangeListener(any());
doNothing().when(mContentView).removeOnSystemUiVisibilityChangeListener(any());
doNothing().when(mContentView).addOnHierarchyChangeListener(any());
doNothing().when(mContentView).addOnSystemUiVisibilityChangeListener(any());
ChromeFullscreenManager fullscreenManager = new ChromeFullscreenManager(
mActivity, ChromeFullscreenManager.ControlsPosition.TOP);
mFullscreenManager = spy(fullscreenManager);
mFullscreenManager.initialize(
mControlContainer, mTabModelSelector, R.dimen.control_container_height);
mFullscreenManager.addListener(mFullscreenListener);
when(mFullscreenManager.getTab()).thenReturn(mTab);
}
@Test
public void testInitialTopControlsHeight() {
assertEquals("Wrong initial top controls height.", TOOLBAR_HEIGHT,
mFullscreenManager.getTopControlsHeight());
}
@Test
public void testListenersNotifiedOfTopControlsHeightChange() {
final int topControlsHeight = TOOLBAR_HEIGHT + EXTRA_TOP_CONTROL_HEIGHT;
final int topControlsMinHeight = EXTRA_TOP_CONTROL_HEIGHT;
mFullscreenManager.setTopControlsHeight(topControlsHeight, topControlsMinHeight);
verify(mFullscreenListener)
.onTopControlsHeightChanged(topControlsHeight, topControlsMinHeight);
}
// controlsResizeView tests ---
// For these tests, we will simulate the scrolls assuming we either completely show or hide (or
// scroll until the min-height) the controls and don't leave at in-between positions. The reason
// is that ChromeFullscreenManager only flips the mControlsResizeView bit if the controls are
// idle, meaning they're at the min-height or fully shown. Making sure the controls snap to
// these two positions is not CFM's responsibility as it's handled in native code by compositor
// or blink.
@Test
public void testControlsResizeViewChanges() {
// Let's use simpler numbers for this test.
final int topHeight = 100;
final int topMinHeight = 0;
TabModelSelectorTabObserver tabFullscreenObserver =
mFullscreenManager.getTabFullscreenObserverForTesting();
mFullscreenManager.setTopControlsHeight(topHeight, topMinHeight);
// Send initial offsets.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ 0,
/*bottomControlsOffsetY*/ 0, /*contentOffsetY*/ 100,
/*topControlsMinHeightOffsetY*/ 0, /*bottomControlsMinHeightOffsetY*/ 0);
// Initially, the controls should be fully visible.
assertTrue("Browser controls aren't fully visible.",
mFullscreenManager.areBrowserControlsFullyVisible());
assertTrue("ControlsResizeView is false,"
+ " but it should be true when the controls are fully visible.",
mFullscreenManager.controlsResizeView());
// Scroll to fully hidden.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ -100,
/*bottomControlsOffsetY*/ 0, /*contentOffsetY*/ 0,
/*topControlsMinHeightOffsetY*/ 0, /*bottomControlsMinHeightOffsetY*/ 0);
assertTrue("Browser controls aren't at min-height.",
mFullscreenManager.areBrowserControlsAtMinHeight());
assertFalse("ControlsResizeView is true,"
+ " but it should be false when the controls are hidden.",
mFullscreenManager.controlsResizeView());
// Now, scroll back to fully visible.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ 0,
/*bottomControlsOffsetY*/ 0, /*contentOffsetY*/ 100,
/*topControlsMinHeightOffsetY*/ 0, /*bottomControlsMinHeightOffsetY*/ 0);
assertFalse("Browser controls are hidden when they should be fully visible.",
mFullscreenManager.areBrowserControlsAtMinHeight());
assertTrue("Browser controls aren't fully visible.",
mFullscreenManager.areBrowserControlsFullyVisible());
// #controlsResizeView should be flipped back to true.
assertTrue("ControlsResizeView is false,"
+ " but it should be true when the controls are fully visible.",
mFullscreenManager.controlsResizeView());
}
@Test
public void testControlsResizeViewChangesWithMinHeight() {
// Let's use simpler numbers for this test. We'll simulate the scrolling logic in the
// compositor. Which means the top and bottom controls will have the same normalized ratio.
// E.g. if the top content offset is 25 (at min-height so the normalized ratio is 0), the
// bottom content offset will be 0 (min-height-0 + normalized-ratio-0 * rest-of-height-60).
final int topHeight = 100;
final int topMinHeight = 25;
final int bottomHeight = 60;
final int bottomMinHeight = 0;
TabModelSelectorTabObserver tabFullscreenObserver =
mFullscreenManager.getTabFullscreenObserverForTesting();
mFullscreenManager.setTopControlsHeight(topHeight, topMinHeight);
mFullscreenManager.setBottomControlsHeight(bottomHeight, bottomMinHeight);
// Send initial offsets.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ 0,
/*bottomControlsOffsetY*/ 0, /*contentOffsetY*/ 100,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
// Initially, the controls should be fully visible.
assertTrue("Browser controls aren't fully visible.",
mFullscreenManager.areBrowserControlsFullyVisible());
assertTrue("ControlsResizeView is false,"
+ " but it should be true when the controls are fully visible.",
mFullscreenManager.controlsResizeView());
// Scroll all the way to the min-height.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ -75,
/*bottomControlsOffsetY*/ 60, /*contentOffsetY*/ 25,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
assertTrue("Browser controls aren't at min-height.",
mFullscreenManager.areBrowserControlsAtMinHeight());
assertFalse("ControlsResizeView is true,"
+ " but it should be false when the controls are at min-height.",
mFullscreenManager.controlsResizeView());
// Now, scroll back to fully visible.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ 0,
/*bottomControlsOffsetY*/ 0, /*contentOffsetY*/ 100,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
assertFalse("Browser controls are at min-height when they should be fully visible.",
mFullscreenManager.areBrowserControlsAtMinHeight());
assertTrue("Browser controls aren't fully visible.",
mFullscreenManager.areBrowserControlsFullyVisible());
// #controlsResizeView should be flipped back to true.
assertTrue("ControlsResizeView is false,"
+ " but it should be true when the controls are fully visible.",
mFullscreenManager.controlsResizeView());
}
@Test
public void testControlsResizeViewWhenControlsAreNotIdle() {
// Let's use simpler numbers for this test. We'll simulate the scrolling logic in the
// compositor. Which means the top and bottom controls will have the same normalized ratio.
// E.g. if the top content offset is 25 (at min-height so the normalized ratio is 0), the
// bottom content offset will be 0 (min-height-0 + normalized-ratio-0 * rest-of-height-60).
final int topHeight = 100;
final int topMinHeight = 25;
final int bottomHeight = 60;
final int bottomMinHeight = 0;
TabModelSelectorTabObserver tabFullscreenObserver =
mFullscreenManager.getTabFullscreenObserverForTesting();
mFullscreenManager.setTopControlsHeight(topHeight, topMinHeight);
mFullscreenManager.setBottomControlsHeight(bottomHeight, bottomMinHeight);
// Send initial offsets.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ 0,
/*bottomControlsOffsetY*/ 0, /*contentOffsetY*/ 100,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
assertTrue("ControlsResizeView is false,"
+ " but it should be true when the controls are fully visible.",
mFullscreenManager.controlsResizeView());
// Scroll a little hide the controls partially.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ -25,
/*bottomControlsOffsetY*/ 20, /*contentOffsetY*/ 75,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
assertTrue("ControlsResizeView is false, but it should still be true.",
mFullscreenManager.controlsResizeView());
// Scroll controls all the way to the min-height.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ -75,
/*bottomControlsOffsetY*/ 60, /*contentOffsetY*/ 25,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
assertFalse("ControlsResizeView is true,"
+ " but it should've flipped to false since the controls are idle now.",
mFullscreenManager.controlsResizeView());
// Scroll controls to show a little more.
tabFullscreenObserver.onBrowserControlsOffsetChanged(mTab, /*topControlsOffsetY*/ -50,
/*bottomControlsOffsetY*/ 40, /*contentOffsetY*/ 50,
/*topControlsMinHeightOffsetY*/ 25, /*bottomControlsMinHeightOffsetY*/ 0);
assertFalse("ControlsResizeView is true, but it should still be false.",
mFullscreenManager.controlsResizeView());
}
// --- controlsResizeView tests
}

@ -1267,7 +1267,8 @@ void WebViewImpl::UpdateICBAndResizeViewport(
if (GetBrowserControls().PermittedState() ==
cc::BrowserControlsState::kBoth &&
!GetBrowserControls().ShrinkViewport()) {
icb_size.Expand(0, -GetBrowserControls().TotalHeight());
icb_size.Expand(0, -(GetBrowserControls().TotalHeight() -
GetBrowserControls().TotalMinHeight()));
}
GetPageScaleConstraintsSet().DidChangeInitialContainingBlockSize(icb_size);

@ -41,6 +41,7 @@ class CORE_EXPORT BrowserControls final
float BottomHeight() const { return params_.bottom_controls_height; }
float BottomMinHeight() const { return params_.bottom_controls_min_height; }
float TotalHeight() const { return TopHeight() + BottomHeight(); }
float TotalMinHeight() const { return TopMinHeight() + BottomMinHeight(); }
bool ShrinkViewport() const {
return params_.browser_controls_shrink_blink_size;
}

@ -1135,6 +1135,140 @@ TEST_F(BrowserControlsTest, MAYBE(DontAffectVHUnitsUseLayoutSize)) {
EXPECT_EQ(800, GetFrame()->View()->ViewportSizeForViewportUnits().Height());
}
// Ensure that vh units are correctly calculated when a top controls min-height
// is set.
TEST_F(BrowserControlsTest, MAYBE(VHUnitsWithTopMinHeight)) {
// Initialize with the browser controls showing.
// Top controls height: 100, top controls min-height: 20.
WebViewImpl* web_view = Initialize("vh-height.html");
web_view->ResizeWithBrowserControls(WebSize(400, 300), WebSize(400, 300),
{100, 20, 0, 0, false, true});
web_view->GetBrowserControls().UpdateConstraintsAndState(
cc::BrowserControlsState::kBoth, cc::BrowserControlsState::kShown);
web_view->GetBrowserControls().SetShownRatio(1, 1);
UpdateAllLifecyclePhases();
ASSERT_EQ(100.f, web_view->GetBrowserControls().ContentOffset());
// 'vh' units should be based on the viewport when the browser controls are
// hidden. However, the viewport height will be limited by the min-height
// since the top controls can't completely hide.
Element* abs_pos = GetElementById(WebString::FromUTF8("abs"));
Element* fixed_pos = GetElementById(WebString::FromUTF8("fixed"));
float div_height = 0.5f * (300 + (100 - 20));
EXPECT_FLOAT_EQ(div_height, abs_pos->getBoundingClientRect()->height());
EXPECT_FLOAT_EQ(div_height, fixed_pos->getBoundingClientRect()->height());
// The size used for viewport units should be reduced by the top controls
// min-height.
EXPECT_EQ(380, GetFrame()->View()->ViewportSizeForViewportUnits().Height());
// Scroll the top controls to hide. They won't scroll past the min-height.
VerticalScroll(-100.f);
web_view->ResizeWithBrowserControls(WebSize(400, 380), WebSize(400, 380),
{100, 20, 0, 0, false, false});
UpdateAllLifecyclePhases();
ASSERT_EQ(20.f, web_view->GetBrowserControls().ContentOffset());
// vh units should be static with respect to the browser controls so neither
// <div> should change size are a result of the browser controls hiding.
EXPECT_FLOAT_EQ(190.f, abs_pos->getBoundingClientRect()->height());
EXPECT_FLOAT_EQ(190.f, fixed_pos->getBoundingClientRect()->height());
// The viewport size used for vh units should not change as a result of top
// controls hiding.
ASSERT_EQ(380, GetFrame()->View()->ViewportSizeForViewportUnits().Height());
}
// Ensure that vh units are correctly calculated when a bottom controls
// min-height is set.
TEST_F(BrowserControlsTest, MAYBE(VHUnitsWithBottomMinHeight)) {
// Initialize with the browser controls showing.
// Top controls height: 100, top controls min-height: 20.
// Bottom controls height: 50, bottom controls min-height: 10.
WebViewImpl* web_view = Initialize("vh-height.html");
web_view->ResizeWithBrowserControls(WebSize(400, 250), WebSize(400, 250),
{100, 20, 50, 10, false, true});
web_view->GetBrowserControls().UpdateConstraintsAndState(
cc::BrowserControlsState::kBoth, cc::BrowserControlsState::kShown);
web_view->GetBrowserControls().SetShownRatio(1, 1);
UpdateAllLifecyclePhases();
EXPECT_FLOAT_EQ(100.f, web_view->GetBrowserControls().ContentOffset());
// 'vh' units should be based on the viewport when the browser controls are
// hidden. However, the viewport height will be limited by the min-height
// since the top and bottom controls can't completely hide.
Element* abs_pos = GetElementById(WebString::FromUTF8("abs"));
Element* fixed_pos = GetElementById(WebString::FromUTF8("fixed"));
float div_height = 0.5f * (250 + (100 - 20) + (50 - 10));
EXPECT_FLOAT_EQ(div_height, abs_pos->getBoundingClientRect()->height());
EXPECT_FLOAT_EQ(div_height, fixed_pos->getBoundingClientRect()->height());
// The size used for viewport units should be reduced by the top/bottom
// controls min-height.
EXPECT_EQ(370, GetFrame()->View()->ViewportSizeForViewportUnits().Height());
// Scroll the controls to hide. They won't scroll past the min-height.
VerticalScroll(-100.f);
web_view->ResizeWithBrowserControls(WebSize(400, 370), WebSize(400, 370),
{100, 20, 50, 10, false, false});
UpdateAllLifecyclePhases();
EXPECT_FLOAT_EQ(20.f, web_view->GetBrowserControls().ContentOffset());
EXPECT_FLOAT_EQ(10.f, web_view->GetBrowserControls().BottomContentOffset());
// vh units should be static with respect to the browser controls so neither
// <div> should change size are a result of the browser controls hiding.
EXPECT_FLOAT_EQ(185.f, abs_pos->getBoundingClientRect()->height());
EXPECT_FLOAT_EQ(185.f, fixed_pos->getBoundingClientRect()->height());
// The viewport size used for vh units should not change as a result of the
// controls hiding.
ASSERT_EQ(370, GetFrame()->View()->ViewportSizeForViewportUnits().Height());
}
// Ensure that vh units are correctly calculated with changing min-heights.
TEST_F(BrowserControlsTest, MAYBE(VHUnitsWithMinHeightsChanging)) {
// Initialize with the browser controls showing.
// Top controls height: 100, top controls min-height: 20.
// Bottom controls height: 50, bottom controls min-height: 10.
WebViewImpl* web_view = Initialize("vh-height.html");
web_view->ResizeWithBrowserControls(WebSize(400, 250), WebSize(400, 250),
{100, 20, 50, 10, false, true});
web_view->GetBrowserControls().UpdateConstraintsAndState(
cc::BrowserControlsState::kBoth, cc::BrowserControlsState::kShown);
web_view->GetBrowserControls().SetShownRatio(1, 1);
UpdateAllLifecyclePhases();
EXPECT_FLOAT_EQ(100.f, web_view->GetBrowserControls().ContentOffset());
// 'vh' units should be based on the viewport when the browser controls are
// hidden. However, the viewport height will be limited by the min-height
// since the top and bottom controls can't completely hide.
Element* abs_pos = GetElementById(WebString::FromUTF8("abs"));
Element* fixed_pos = GetElementById(WebString::FromUTF8("fixed"));
float div_height = 0.5f * (250 + (100 - 20) + (50 - 10));
EXPECT_FLOAT_EQ(div_height, abs_pos->getBoundingClientRect()->height());
EXPECT_FLOAT_EQ(div_height, fixed_pos->getBoundingClientRect()->height());
// The size used for viewport units should be reduced by the top/bottom
// controls min-height.
EXPECT_EQ(370, GetFrame()->View()->ViewportSizeForViewportUnits().Height());
// Make the min-heights 0.
web_view->ResizeWithBrowserControls(WebSize(400, 250), WebSize(400, 250),
{100, 0, 50, 0, false, true});
UpdateAllLifecyclePhases();
// The viewport size used for vh units should be updated to reflect the change
// to the min-heights.
float height = 250 + (100 - 0) + (50 - 0);
ASSERT_EQ(height,
GetFrame()->View()->ViewportSizeForViewportUnits().Height());
}
// This tests that the viewport remains anchored when browser controls are
// brought in while the document is fully scrolled. This normally causes
// clamping of the visual viewport to keep it bounded by the layout viewport

@ -1024,8 +1024,9 @@ FloatSize LocalFrameView::ViewportSizeForViewportUnits() const {
// zoom factor when use-zoom-for-dsf is enabled on Android. Confirm this
// works correctly when that's turned on. https://crbug.com/737777.
float page_scale_at_layout_width = viewport_width / layout_size.Width();
layout_size.Expand(
0, browser_controls.TotalHeight() / page_scale_at_layout_width);
layout_size.Expand(0, (browser_controls.TotalHeight() -
browser_controls.TotalMinHeight()) /
page_scale_at_layout_width);
}
}