blob: 6b03ab8e17b59aae62fc24a8271c39e8ab4955fc [file] [log] [blame]
// Copyright 2018 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.
#include "chrome/browser/ui/views/frame/top_controls_slide_controller.h"
#include <memory>
#include <numeric>
#include <tuple>
#include <vector>
#include "ash/constants/ash_switches.h"
#include "ash/public/ash_interfaces.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/mojom/cros_display_config.mojom-test-utils.h"
#include "ash/public/mojom/cros_display_config.mojom.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/path_service.h"
#include "base/strings/safe_sprintf.h"
#include "build/chromeos_buildflags.h"
#include "cc/base/math_util.h"
#include "chrome/browser/ash/accessibility/accessibility_manager.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/bookmarks/bookmark_bar_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/top_container_view.h"
#include "chrome/browser/ui/views/location_bar/location_bar_view.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/request_type.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/dns/mock_host_resolver.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/event_constants.h"
#include "ui/events/test/event_generator.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
namespace {
using ::ash::AccessibilityManager;
enum class TopChromeShownState {
kFullyShown,
kFullyHidden,
};
enum class ScrollDirection {
kUp,
kDown,
};
// Checks that the translation part of the two given transforms are equal.
void CompareTranslations(const gfx::Transform& t1, const gfx::Transform& t2) {
const gfx::Vector2dF t1_translation = t1.To2dTranslation();
const gfx::Vector2dF t2_translation = t2.To2dTranslation();
EXPECT_FLOAT_EQ(t1_translation.x(), t2_translation.x());
EXPECT_FLOAT_EQ(t1_translation.y(), t2_translation.y());
}
content::RenderWidgetHost* GetRenderWidgetHost(content::WebContents* contents) {
EXPECT_TRUE(contents);
return contents->GetRenderWidgetHostView()->GetRenderWidgetHost();
}
void SynchronizeBrowserWithRenderer(content::WebContents* contents) {
content::MainThreadFrameObserver frame_observer(
GetRenderWidgetHost(contents));
frame_observer.Wait();
}
// A test view that will be added as a child to the BrowserView to verify how
// many times it's laid out while sliding is in progress.
class LayoutTestView : public views::View {
public:
explicit LayoutTestView(BrowserView* parent) {
DCHECK(parent);
parent->AddChildView(this);
parent->Layout();
layout_count_ = 0;
}
~LayoutTestView() override = default;
LayoutTestView(const LayoutTestView&) = delete;
LayoutTestView& operator=(const LayoutTestView&) = delete;
int layout_count() const { return layout_count_; }
void Reset() {
InvalidateLayout();
layout_count_ = 0;
}
// views::View:
void Layout() override { ++layout_count_; }
private:
int layout_count_ = 0;
};
class TestControllerObserver {
public:
virtual void OnShownRatioChanged(float shown_ratio) = 0;
virtual void OnGestureScrollInProgressChanged(bool in_progress) = 0;
protected:
virtual ~TestControllerObserver() = default;
};
// Defines a wrapper around the real TopControlsSlideControllerChromeOS which
// will be injected in the BrowserView. This is used to intercept the calls to
// the real controller here in the tests witout affecting the production code.
// An object of this class owns the instance of the real controller, and itself
// is owned by the BrowserView (See
// BrowserView::InjectTopControlsSlideControllerForTesting()).
class TestController : public TopControlsSlideController {
public:
explicit TestController(
std::unique_ptr<TopControlsSlideController> real_controller)
: real_controller_(std::move(real_controller)) {
DCHECK(real_controller_);
}
TestController(const TestController&) = delete;
TestController& operator=(const TestController&) = delete;
~TestController() override = default;
void AddObserver(TestControllerObserver* observer) {
observers_.AddObserver(observer);
}
void RemoveObserver(TestControllerObserver* observer) {
observers_.RemoveObserver(observer);
}
// TopControlsSlideController:
bool IsEnabled() const override { return real_controller_->IsEnabled(); }
float GetShownRatio() const override {
return real_controller_->GetShownRatio();
}
void SetShownRatio(content::WebContents* contents, float ratio) override {
real_controller_->SetShownRatio(contents, ratio);
for (auto& observer : observers_)
observer.OnShownRatioChanged(ratio);
}
void OnBrowserFullscreenStateWillChange(bool new_fullscreen_state) override {
real_controller_->OnBrowserFullscreenStateWillChange(new_fullscreen_state);
}
bool DoBrowserControlsShrinkRendererSize(
const content::WebContents* contents) const override {
return real_controller_->DoBrowserControlsShrinkRendererSize(contents);
}
void SetTopControlsGestureScrollInProgress(bool in_progress) override {
real_controller_->SetTopControlsGestureScrollInProgress(in_progress);
for (auto& observer : observers_)
observer.OnGestureScrollInProgressChanged(in_progress);
}
bool IsTopControlsGestureScrollInProgress() const override {
return real_controller_->IsTopControlsGestureScrollInProgress();
}
bool IsTopControlsSlidingInProgress() const override {
return real_controller_->IsTopControlsSlidingInProgress();
}
private:
std::unique_ptr<TopControlsSlideController> real_controller_;
base::ObserverList<TestControllerObserver>::Unchecked observers_;
};
// Waits for a given terminal value (1.f or 0.f) of the browser top controls
// shown ratio on a given browser window.
class TopControlsShownRatioWaiter : public TestControllerObserver {
public:
explicit TopControlsShownRatioWaiter(TestController* controller)
: controller_(controller) {
controller_->AddObserver(this);
}
TopControlsShownRatioWaiter(const TopControlsShownRatioWaiter&) = delete;
TopControlsShownRatioWaiter& operator=(const TopControlsShownRatioWaiter&) =
delete;
~TopControlsShownRatioWaiter() override { controller_->RemoveObserver(this); }
// TestControllerObserver:
void OnShownRatioChanged(float shown_ratio) override { CheckRatio(); }
void OnGestureScrollInProgressChanged(bool in_progress) override {
CheckRatio();
}
void WaitForRatio(float ratio) {
DCHECK(ratio == 1.f || ratio == 0.f) << "Should only be used to wait for "
"terminal values of the shown "
"ratio.";
waiting_for_shown_ratio_ = ratio;
if (CheckRatio())
return;
// Use kNestableTasksAllowed to make it possible to wait inside a posted
// task.
run_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
run_loop_->Run();
}
private:
bool CheckRatio() {
// To avoid flakes, we also check that gesture scrolling is not in progress
// which means for a terminal value of the shown ratio (that we're waiting
// for) sliding is also not in progress and we reached a steady state.
if (!controller_->IsTopControlsGestureScrollInProgress() &&
cc::MathUtil::IsWithinEpsilon(controller_->GetShownRatio(),
waiting_for_shown_ratio_)) {
if (run_loop_)
run_loop_->Quit();
return true;
}
return false;
}
TestController* controller_;
std::unique_ptr<base::RunLoop> run_loop_;
float waiting_for_shown_ratio_ = 0;
};
// Waits for a given |is_gesture_scrolling_in_progress_| value.
class GestureScrollInProgressChangeWaiter : public TestControllerObserver {
public:
explicit GestureScrollInProgressChangeWaiter(TestController* controller)
: controller_(controller) {
controller_->AddObserver(this);
}
~GestureScrollInProgressChangeWaiter() override {
controller_->RemoveObserver(this);
}
GestureScrollInProgressChangeWaiter(
const GestureScrollInProgressChangeWaiter&) = delete;
GestureScrollInProgressChangeWaiter& operator=(
const GestureScrollInProgressChangeWaiter&) = delete;
// TestControllerObserver:
void OnShownRatioChanged(float shown_ratio) override {}
void OnGestureScrollInProgressChanged(bool in_progress) override {
if (in_progress == waited_for_in_progress_state_ && run_loop_)
std::move(run_loop_)->Quit();
}
void WaitForInProgressState(bool in_progress_state) {
if (controller_->IsTopControlsGestureScrollInProgress() ==
in_progress_state)
return;
waited_for_in_progress_state_ = in_progress_state;
// Use kNestableTasksAllowed to make it possible to wait inside a posted
// task.
run_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
run_loop_->Run();
}
private:
TestController* controller_;
std::unique_ptr<base::RunLoop> run_loop_;
bool waited_for_in_progress_state_ = false;
};
} // namespace
class TopControlsSlideControllerTest : public InProcessBrowserTest {
public:
TopControlsSlideControllerTest() = default;
TopControlsSlideControllerTest(const TopControlsSlideControllerTest&) =
delete;
TopControlsSlideControllerTest& operator=(
const TopControlsSlideControllerTest&) = delete;
~TopControlsSlideControllerTest() override = default;
BrowserView* browser_view() const {
return BrowserView::GetBrowserViewForBrowser(browser());
}
TestController* top_controls_slide_controller() const {
DCHECK(test_controller_);
return test_controller_;
}
// InProcessBrowserTest:
void SetUpDefaultCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpDefaultCommandLine(command_line);
// Mark the device is capable of entering tablet mode.
command_line->AppendSwitch(ash::switches::kAshEnableTabletMode);
// Use first display as internal display. Otherwise, tablet mode is ended
// on display change.
command_line->AppendSwitch(switches::kUseFirstDisplayAsInternal);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
// Add content/test/data so we can use cross_site_iframe_factory.html
base::FilePath test_data_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &test_data_dir));
embedded_test_server()->ServeFilesFromDirectory(
test_data_dir.AppendASCII("chrome/test/data/top_controls_scroll"));
ASSERT_TRUE(embedded_test_server()->Start());
InjectTestController();
}
void InjectTestController() {
browser_view()->top_controls_slide_controller_ = CreateTestController(
std::move(browser_view()->top_controls_slide_controller_));
}
void OpenUrlAtIndex(const GURL& url, int index) {
AddTabAtIndex(index, url, ui::PAGE_TRANSITION_TYPED);
auto* active_contents = browser_view()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(active_contents));
SynchronizeBrowserWithRenderer(active_contents);
}
void NavigateActiveTabToUrl(const GURL& url) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
auto* active_contents = browser_view()->GetActiveWebContents();
EXPECT_TRUE(content::WaitForLoadStop(active_contents));
SynchronizeBrowserWithRenderer(active_contents);
}
void ToggleTabletMode() {
TopControlsShownRatioWaiter waiter{test_controller_};
ash::ShellTestApi().SetTabletModeEnabledForTest(!GetTabletModeEnabled());
waiter.WaitForRatio(1.f);
}
bool GetTabletModeEnabled() const {
return ash::TabletMode::Get()->InTabletMode();
}
void CheckBrowserLayout(BrowserView* browser_view,
TopChromeShownState shown_state) const {
const int top_controls_height = browser_view->GetTopControlsHeight();
EXPECT_NE(top_controls_height, 0);
ui::Layer* root_view_layer = browser_view->frame()->GetRootView()->layer();
// The fully-shown and fully-hidden states are terminal states. We check
// when we reach the steady state. The root view should not have a layer
// now.
ASSERT_FALSE(root_view_layer);
// The contents layer transform should be restored to identity.
const gfx::Transform expected_transform;
EXPECT_FALSE(browser_view->GetNativeViewHostsForTopControlsSlide().empty());
for (auto* host : browser_view->GetNativeViewHostsForTopControlsSlide()) {
ASSERT_TRUE(host->GetNativeViewContainer());
ASSERT_TRUE(host->GetNativeViewContainer()->layer());
CompareTranslations(expected_transform,
host->GetNativeViewContainer()->layer()->transform());
}
// The BrowserView layout should be adjusted properly:
const gfx::Rect& top_container_bounds =
browser_view->top_container()->bounds();
EXPECT_EQ(top_container_bounds.height(), top_controls_height);
const int top_container_bottom = top_container_bounds.bottom();
const gfx::Rect& contents_container_bounds =
browser_view->contents_container()->bounds();
EXPECT_EQ(top_container_bottom, contents_container_bounds.y());
if (shown_state == TopChromeShownState::kFullyHidden) {
// Top container is shifted up.
EXPECT_EQ(top_container_bounds.y(), -top_controls_height);
// Contents should occupy the entire height of the browser view.
EXPECT_EQ(browser_view->height(),
browser_view->contents_container()->height());
// Widget should not allow things to show outside its bounds.
EXPECT_TRUE(browser_view->frame()->GetLayer()->GetMasksToBounds());
// The browser controls doesn't shrink the blink viewport size.
EXPECT_FALSE(browser_view->DoBrowserControlsShrinkRendererSize(
browser_view->GetActiveWebContents()));
} else {
// Top container start at the top.
EXPECT_EQ(top_container_bounds.y(), 0);
// Contents should occupy the remainder of the browser view after the top
// container.
EXPECT_EQ(browser_view->height() - top_controls_height,
browser_view->contents_container()->height());
EXPECT_FALSE(browser_view->frame()->GetLayer()->GetMasksToBounds());
// The browser controls does shrink the blink viewport size.
EXPECT_TRUE(browser_view->DoBrowserControlsShrinkRendererSize(
browser_view->GetActiveWebContents()));
}
}
// Verifies the state of the browser window when the active page is being
// scrolled by touch gestures in such a way that will result in the top
// controls shown ratio becoming a fractional value (i.e. sliding top-chrome
// is in progress).
// The |layout_test_view| will be used to verify that the BrowserView doesn't
// get laid out more than once while sliding is in progress.
// The |expected_shrink_renderer_size| will be checked against the
// `DoBrowserControlsShrinkRendererSize` bit while sliding.
void CheckIntermediateScrollStep(LayoutTestView* layout_test_view,
bool expected_shrink_renderer_size) {
const float shown_ratio = top_controls_slide_controller()->GetShownRatio();
// This should only be used to verify the state of the browser while sliding
// is in progress.
ASSERT_TRUE(shown_ratio != 1.f && shown_ratio != 0.f);
// While scrolling is in progress, only a single layout is expected.
EXPECT_TRUE(
top_controls_slide_controller()->IsTopControlsSlidingInProgress());
EXPECT_EQ(layout_test_view->layout_count(), 1);
const int top_controls_height = browser_view()->GetTopControlsHeight();
EXPECT_NE(top_controls_height, 0);
ui::Layer* root_view_layer =
browser_view()->frame()->GetRootView()->layer();
// While sliding is in progress, the root view paints to a layer.
ASSERT_TRUE(root_view_layer);
// This will be called repeatedly while scrolling is in progress. The
// `DoBrowserControlsShrinkRendererSize` bit should remain the same as the
// expected value.
EXPECT_EQ(expected_shrink_renderer_size,
browser_view()->DoBrowserControlsShrinkRendererSize(
browser_view()->GetActiveWebContents()));
// Check intermediate transforms.
gfx::Transform expected_transform;
const float y_translation = top_controls_height * (shown_ratio - 1.f);
expected_transform.Translate(0, y_translation);
EXPECT_FALSE(
browser_view()->GetNativeViewHostsForTopControlsSlide().empty());
for (auto* host : browser_view()->GetNativeViewHostsForTopControlsSlide()) {
ASSERT_TRUE(host->GetNativeViewContainer());
ASSERT_TRUE(host->GetNativeViewContainer()->layer());
CompareTranslations(expected_transform,
host->GetNativeViewContainer()->layer()->transform());
}
CompareTranslations(expected_transform, root_view_layer->transform());
}
// Using the given |generator| and the start and end points, it generates a
// gesture scroll sequence with appropriate velocity so that fling gesture
// scrolls are generated.
void GenerateGestureFlingScrollSequence(ui::test::EventGenerator* generator,
const gfx::Point& start_point,
const gfx::Point& end_point) {
DCHECK(generator);
content::WebContents* contents = browser_view()->GetActiveWebContents();
generator->GestureScrollSequenceWithCallback(
start_point, end_point,
generator->CalculateScrollDurationForFlingVelocity(
start_point, end_point, 100 /* velocity */, 2 /* steps */),
2 /* steps */,
base::BindRepeating(
[](content::WebContents* contents, ui::EventType,
const gfx::Vector2dF&) {
// Give the event a chance to propagate to renderer before sending
// the next one.
SynchronizeBrowserWithRenderer(contents);
},
contents));
}
// Generates a gesture fling scroll sequence to scroll the current page in the
// given |direction|, and waits for and verifies that top-chrome reaches the
// given |target_state|.
void ScrollAndExpectTopChromeToBe(ScrollDirection direction,
TopChromeShownState target_state) {
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow(),
browser_window);
const gfx::Point start_point =
browser_window->GetBoundsInScreen().CenterPoint();
const gfx::Point end_point =
start_point +
gfx::Vector2d(0, direction == ScrollDirection::kDown ? -100 : 100);
const float target_ratio =
target_state == TopChromeShownState::kFullyHidden ? 0.f : 1.f;
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
GenerateGestureFlingScrollSequence(&event_generator, start_point,
end_point);
waiter.WaitForRatio(target_ratio);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(),
target_ratio);
CheckBrowserLayout(browser_view(), target_state);
}
private:
std::unique_ptr<TopControlsSlideController> CreateTestController(
std::unique_ptr<TopControlsSlideController> real_controller) {
DCHECK(real_controller);
auto controller =
std::make_unique<TestController>(std::move(real_controller));
test_controller_ = controller.get();
return std::move(controller);
}
TestController* test_controller_ = nullptr; // Not owned.
};
namespace {
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, DisabledForHostedApps) {
browser()->window()->Close();
// Open a new app window.
Browser::CreateParams params = Browser::CreateParams::CreateForApp(
"test_browser_app", true /* trusted_source */, gfx::Rect(),
browser()->profile(), true);
params.initial_show_state = ui::SHOW_STATE_DEFAULT;
Browser* browser = Browser::Create(params);
AddBlankTabAndShow(browser);
ASSERT_TRUE(browser->is_type_app());
// No slide controller gets created for hosted apps.
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
EXPECT_FALSE(browser_view->top_controls_slide_controller());
// Renderer will get a zero-height top controls.
EXPECT_EQ(browser_view->GetTopControlsHeight(), 0);
EXPECT_FALSE(browser_view->DoBrowserControlsShrinkRendererSize(
browser_view->GetActiveWebContents()));
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
EnabledOnlyForTabletNonImmersiveModes) {
EXPECT_FALSE(GetTabletModeEnabled());
AddBlankTabAndShow(browser());
// For a normal browser, the controller is created.
ASSERT_TRUE(top_controls_slide_controller());
// But the behavior is disabled since we didn't go to tablet mode yet.
EXPECT_FALSE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// The browser reports a zero height for top-chrome UIs when the behavior is
// disabled, so the render doesn't think it needs to move the top controls.
EXPECT_EQ(browser_view()->GetTopControlsHeight(), 0);
EXPECT_FALSE(browser_view()->DoBrowserControlsShrinkRendererSize(
browser_view()->GetActiveWebContents()));
// Now enable tablet mode.
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
// The behavior is enabled, but the shown ratio remains at 1.f since no page
// scrolls happened yet.
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
EXPECT_NE(browser_view()->GetTopControlsHeight(), 0);
EXPECT_TRUE(browser_view()->DoBrowserControlsShrinkRendererSize(
browser_view()->GetActiveWebContents()));
// Immersive fullscreen mode disables the behavior.
chrome::ToggleFullscreenMode(browser());
EXPECT_TRUE(browser_view()->IsFullscreen());
EXPECT_FALSE(top_controls_slide_controller()->IsEnabled());
EXPECT_EQ(browser_view()->GetTopControlsHeight(), 0);
EXPECT_FALSE(browser_view()->DoBrowserControlsShrinkRendererSize(
browser_view()->GetActiveWebContents()));
// Exit immersive mode.
chrome::ToggleFullscreenMode(browser());
EXPECT_FALSE(browser_view()->IsFullscreen());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_NE(browser_view()->GetTopControlsHeight(), 0);
EXPECT_TRUE(browser_view()->DoBrowserControlsShrinkRendererSize(
browser_view()->GetActiveWebContents()));
ToggleTabletMode();
EXPECT_FALSE(GetTabletModeEnabled());
EXPECT_FALSE(top_controls_slide_controller()->IsEnabled());
EXPECT_EQ(browser_view()->GetTopControlsHeight(), 0);
EXPECT_FALSE(browser_view()->DoBrowserControlsShrinkRendererSize(
browser_view()->GetActiveWebContents()));
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, TestScrollingPage) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Navigate to our test page that has a long vertical content which we can use
// to test page scrolling.
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
// It's possible to hide top chrome with gesture scrolling.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// Perform another gesture scroll in the opposite direction and expect top-
// chrome to be fully shown.
ScrollAndExpectTopChromeToBe(ScrollDirection::kUp,
TopChromeShownState::kFullyShown);
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, TestCtrlL) {
// Switch to tablet mode, and scroll until the top controls are fully hidden.
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
auto* active_contents = browser_view()->GetActiveWebContents();
SCOPED_TRACE("Scrolling to fully hide the top controls.");
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
SynchronizeBrowserWithRenderer(active_contents);
// Hit Ctrl+L which should focus the omnibox. This should unhide the top
// controls.
SCOPED_TRACE("Firing Ctrl+L.");
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow(),
browser_window);
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
event_generator.PressKey(ui::VKEY_L, ui::EF_CONTROL_DOWN);
event_generator.ReleaseKey(ui::VKEY_L, ui::EF_CONTROL_DOWN);
waiter.WaitForRatio(1.f);
EXPECT_TRUE(browser_view()->GetLocationBarView()->omnibox_view()->HasFocus());
}
// Fails on Linux ChromiumOS MSan Tests (https://crbug.com/1194575).
#if defined(OS_CHROMEOS)
#define MAYBE_TestScrollingPageAndSwitchingToNTP \
DISABLED_TestScrollingPageAndSwitchingToNTP
#else
#define MAYBE_TestScrollingPageAndSwitchingToNTP \
TestScrollingPageAndSwitchingToNTP
#endif
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
MAYBE_TestScrollingPageAndSwitchingToNTP) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Add a tab containing a local NTP page. NTP pages are not permitted to hide
// top-chrome with scrolling.
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(),
GURL(chrome::kChromeUINewTabURL)));
ASSERT_EQ(browser()->tab_strip_model()->count(), 1);
// Navigate to our test page that has a long vertical content which we can use
// to test page scrolling.
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
// Scroll the `top_controls_scroll.html` page such that top-chrome is now
// fully hidden.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// Simulate (Ctrl + Tab) shortcut to select the next tab. Top-chrome should
// show automatically.
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
browser()->tab_strip_model()->SelectNextTab();
EXPECT_EQ(browser()->tab_strip_model()->active_index(), 1);
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
// Since this is the NTP page, gesture scrolling down will not hide
// top-chrome. It will remain fully shown.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyShown);
// Switch back to the scrollable page, it should be possible now to hide top-
// chrome.
browser()->tab_strip_model()->SelectNextTab();
EXPECT_EQ(browser()->tab_strip_model()->active_index(), 0);
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// The `DoBrowserControlsShrinkRendererSize` bit is separately tracked for
// each tab.
auto* tab_strip_model = browser()->tab_strip_model();
auto* scrollable_page_contents = tab_strip_model->GetWebContentsAt(0);
auto* ntp_contents = tab_strip_model->GetWebContentsAt(1);
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(ntp_contents));
EXPECT_FALSE(browser_view()->DoBrowserControlsShrinkRendererSize(
scrollable_page_contents));
}
// Fails on Linux Chromium OS Tests (https://crbug.com/1191327).
#if defined(OS_CHROMEOS)
#define MAYBE_TestClosingATab DISABLED_TestClosingATab
#else
#define MAYBE_TestClosingATab TestClosingATab
#endif
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, MAYBE_TestClosingATab) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Navigate to our test scrollable page.
NavigateActiveTabToUrl(
embedded_test_server()->GetURL("/top_controls_scroll.html"));
ASSERT_EQ(browser()->tab_strip_model()->count(), 1);
// Scroll to fully hide top-chrome.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// Simulate (Ctrl + T) by inserting a new tab. Expect top-chrome to be fully
// shown.
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
chrome::NewTab(browser());
waiter.WaitForRatio(1.f);
EXPECT_EQ(browser()->tab_strip_model()->active_index(), 1);
EXPECT_EQ(browser()->tab_strip_model()->count(), 2);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
// Now simulate (Ctrl + W) by closing this newly created tab, expect to return
// to the scrollable page tab, which used to have a top-chrome shown ratio of
// 0. We should expect that it will animate back to a shown ratio of 1.
// This test is needed to make sure that the native view of the web contents
// of the newly selected tab after the current one is closed will attach to
// the browser's native view host *before* we attempt to make any changes to
// its top-chrome shown ratio.
chrome::CloseTab(browser());
waiter.WaitForRatio(1.f);
EXPECT_EQ(browser()->tab_strip_model()->active_index(), 0);
EXPECT_EQ(browser()->tab_strip_model()->count(), 1);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
// It is still possible to slide top-chrome up.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
TestFocusEditableElements) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Navigate to our test page that has a long vertical content which we can use
// to test page scrolling.
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
SCOPED_TRACE("Initial scroll to hide the top controls.");
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// Define an internal lambda that returns the javascript function body that
// can be executed on the focus on `top_controls_scroll.html` page to focus on
// an editable text input field in the page, or blur its focus depending on
// the |should_focus| parameter.
auto get_js_function_body = [](bool should_focus) -> std::string {
constexpr size_t kBufferSize = 1024;
char buffer[kBufferSize];
base::strings::SafeSPrintf(
buffer,
"domAutomationController.send("
" ((function() {"
" var editableElement ="
" document.getElementById('editable-element');"
" if (editableElement) {"
" editableElement.%s();"
" return true;"
" }"
" return false;"
" })()));",
should_focus ? "focus" : "blur");
return std::string(buffer);
};
// Focus on the editable element in the page and expect that top-chrome will
// be shown.
SCOPED_TRACE("Focus an editable element in the page.");
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
content::WebContents* contents = browser_view()->GetActiveWebContents();
bool bool_result = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
contents, get_js_function_body(true /* should_focus */), &bool_result));
EXPECT_TRUE(bool_result);
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
// Now try scrolling in a way that would normally hide top-chrome, and expect
// that top-chrome will be forced shown as long as the editable element is
// focused.
SCOPED_TRACE("Attempting a scroll which will not hide the top controls.");
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyShown);
// Now blur the focused editable element. Expect that top-chrome can now be
// hidden with gesture scrolls.
bool_result = false;
SCOPED_TRACE("Bluring the focus away from the editable element.");
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
contents, get_js_function_body(false /* should_focus */), &bool_result));
EXPECT_TRUE(bool_result);
// Evaluate an empty sentence to make sure that the event processing is done
// in the content.
std::ignore = content::EvalJs(contents, ";");
SCOPED_TRACE("Scroll to hide should now work.");
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
}
// Used to wait for the browser view to change its bounds as a result of display
// rotation.
class BrowserViewLayoutWaiter : public views::ViewObserver {
public:
explicit BrowserViewLayoutWaiter(BrowserView* browser_view)
: browser_view_(browser_view) {
browser_view->AddObserver(this);
}
BrowserViewLayoutWaiter(const BrowserViewLayoutWaiter&) = delete;
BrowserViewLayoutWaiter& operator=(const BrowserViewLayoutWaiter&) = delete;
~BrowserViewLayoutWaiter() override { browser_view_->RemoveObserver(this); }
void Wait() {
if (view_bounds_changed_) {
view_bounds_changed_ = false;
return;
}
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
// views::ViewObserver:
void OnViewBoundsChanged(views::View* observed_view) override {
view_bounds_changed_ = true;
if (run_loop_)
run_loop_->Quit();
}
private:
BrowserView* browser_view_;
bool view_bounds_changed_ = false;
std::unique_ptr<base::RunLoop> run_loop_;
};
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, DisplayRotation) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Maximizing the browser window makes the browser view layout more
// predictable with display rotation, as it's just resized to match the
// display bounds.
browser_view()->frame()->Maximize();
// Navigate to our scrollable test page, scroll with touch gestures so that
// top-chrome is fully hidden.
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow(),
browser_window);
gfx::Point start_point = event_generator.current_screen_location();
gfx::Point end_point = start_point + gfx::Vector2d(0, -100);
GenerateGestureFlingScrollSequence(&event_generator, start_point, end_point);
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
waiter.WaitForRatio(0.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 0);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyHidden);
// Try all possible rotations. Changing display rotation should *not* unhide
// top chrome.
const std::vector<ash::mojom::DisplayRotationOptions> rotations_to_try = {
ash::mojom::DisplayRotationOptions::k90Degrees,
ash::mojom::DisplayRotationOptions::k180Degrees,
ash::mojom::DisplayRotationOptions::k270Degrees,
ash::mojom::DisplayRotationOptions::kZeroDegrees,
};
mojo::Remote<ash::mojom::CrosDisplayConfigController> cros_display_config;
ash::BindCrosDisplayConfigController(
cros_display_config.BindNewPipeAndPassReceiver());
ash::mojom::CrosDisplayConfigControllerAsyncWaiter waiter_for(
cros_display_config.get());
std::vector<ash::mojom::DisplayUnitInfoPtr> info_list;
waiter_for.GetDisplayUnitInfoList(false /* single_unified */, &info_list);
for (const ash::mojom::DisplayUnitInfoPtr& display_unit_info : info_list) {
const std::string display_id = display_unit_info->id;
for (const auto& rotation : rotations_to_try) {
BrowserViewLayoutWaiter browser_view_layout_waiter(browser_view());
auto config_properties = ash::mojom::DisplayConfigProperties::New();
config_properties->rotation = ash::mojom::DisplayRotation::New(rotation);
ash::mojom::DisplayConfigResult result;
waiter_for.SetDisplayProperties(display_id, std::move(config_properties),
ash::mojom::DisplayConfigSource::kUser,
&result);
EXPECT_EQ(result, ash::mojom::DisplayConfigResult::kSuccess);
// Wait for the browser view to change its bounds as a result of display
// rotation.
browser_view_layout_waiter.Wait();
// Make sure top-chrome is still hidden.
waiter.WaitForRatio(0.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 0);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyHidden);
// Now perform gesture scrolls to show top-chrome, make sure everything
// looks good in this rotation.
// Update the start and end points after rotation.
start_point = browser_window->GetBoundsInRootWindow().CenterPoint();
end_point = start_point + gfx::Vector2d(0, -100);
// Make sure to send them in screen coordinates to make sure rotation
// is taken into consideration.
browser_window->GetRootWindow()->GetHost()->ConvertDIPToScreenInPixels(
&start_point);
browser_window->GetRootWindow()->GetHost()->ConvertDIPToScreenInPixels(
&end_point);
GenerateGestureFlingScrollSequence(&event_generator, end_point,
start_point);
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
// Scroll again to hide top-chrome in preparation for the next rotation
// iteration.
GenerateGestureFlingScrollSequence(&event_generator, start_point,
end_point);
waiter.WaitForRatio(0.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 0);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyHidden);
}
}
}
// Waits for receiving an IPC message from the render frame that the page state
// has been updated. This makes sure that the renderer now sees the new top
// controls height if it changed.
class PageStateUpdateWaiter : content::WebContentsObserver {
public:
explicit PageStateUpdateWaiter(content::WebContents* contents)
: WebContentsObserver(contents) {}
PageStateUpdateWaiter(const PageStateUpdateWaiter&) = delete;
PageStateUpdateWaiter& operator=(const PageStateUpdateWaiter&) = delete;
~PageStateUpdateWaiter() override = default;
void Wait() {
run_loop_.Run();
SynchronizeBrowserWithRenderer(web_contents());
}
// content::WebContentsObserver:
void NavigationEntryChanged(
const content::EntryChangedDetails& change_details) override {
// This notification is triggered upon receiving the
// |FrameHostMsg_UpdateState| message in the |RenderFrameHostImpl|, which
// indicates that the page state now has been updated, and we can now
// proceeed with testing gesture scrolls behavior.
run_loop_.Quit();
}
private:
base::RunLoop run_loop_;
};
// Verifies that we ignore the shown ratios sent from widgets other than that of
// the main frame (such as widgets of the drop-down menus in web pages).
// https://crbug.com/891471.
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, TestDropDowns) {
browser_view()->frame()->Maximize();
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
// Send a mouse click event that should open the popup drop-down menu of the
// <select> html element on the page.
// Note that if a non-main-frame widget is created, its LayerTreeHostImpl's
// `top_controls_shown_ratio_` (which is initialized to 0.f) will be sent to
// the browser when a new compositor frame gets generated. If this shown ratio
// value is not ignored, top-chrome will immediately hide, which will result
// in a BrowserView's Layout() and the immediate closure of the drop-down
// menu.
// We verify below that this doesn't happen, the menu remains open, and it's
// possible to select another option in the drop-down menu.
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
PageStateUpdateWaiter page_state_update_waiter(contents);
page_state_update_waiter.Wait();
// Programmatically focus the <select> element, and verify that the element
// has been focused.
EXPECT_EQ(true, content::EvalJs(contents, "focusSelectElement();"));
EXPECT_EQ(true, content::EvalJs(contents, "selectFocused;"));
// Hit <enter> on the keyboard, then <down> three times, then <enter> again to
// select the fourth option.
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow());
auto send_key_event = [&event_generator](ui::KeyboardCode keycode) {
event_generator.PressKey(keycode, ui::EF_NONE);
event_generator.ReleaseKey(keycode, ui::EF_NONE);
};
send_key_event(ui::VKEY_RETURN);
send_key_event(ui::VKEY_DOWN);
send_key_event(ui::VKEY_DOWN);
send_key_event(ui::VKEY_DOWN);
send_key_event(ui::VKEY_RETURN);
// Evaluate an empty sentence to make sure that the event processing is done
// in the content.
std::ignore = content::EvalJs(contents, ";");
// Verify that the selected option has changed and the fourth option is
// selected.
EXPECT_EQ(true, content::EvalJs(contents, "selectChanged;"));
EXPECT_EQ("4", content::EvalJs(contents, "getSelectedValue();"));
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
TestScrollingMaximizedPageBeforeGoingToTabletMode) {
// If the page exists in a maximized browser window before going to tablet
// mode, the layout that results from going to tablet mode does not change
// the size of the page viewport. Hence, the visual properties of the renderer
// and the browser are not automatically synchrnoized. But going to tablet
// mode enables the top-chrome sliding feature (i.e.
// BrowserView::GetTopControlsHeight() now returns a non-zero value). We must
// make sure that we synchronize the visual properties manually, otherwise
// the renderer will never get the new top-controls height.
browser_view()->frame()->Maximize();
// Navigate to our test scrollable page.
NavigateActiveTabToUrl(
embedded_test_server()->GetURL("/top_controls_scroll.html"));
content::WebContents* active_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_EQ(browser()->tab_strip_model()->count(), 1);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
EXPECT_EQ(browser_view()->GetTopControlsHeight(), 0);
// Switch to tablet mode. This should trigger a synchronize visual properties
// event with the renderer so that it can get the correct top controls height
// now that the top-chrome slide feature is enabled. Otherwise hiding top
// chrome with gesture scrolls won't be possible at all.
PageStateUpdateWaiter page_state_update_waiter(active_contents);
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
EXPECT_NE(browser_view()->GetTopControlsHeight(), 0);
page_state_update_waiter.Wait();
// Scroll to fully hide top-chrome.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
}
// Waits for a fractional value of the top controls shown ratio, upon which it
// will invoke the |on_intermediate_ratio_callback| which can be used to verify
// the state of the browser while sliding is in progress.
class IntermediateShownRatioWaiter : public TestControllerObserver {
public:
explicit IntermediateShownRatioWaiter(
TestController* controller,
base::OnceClosure on_intermediate_ratio_callback)
: controller_(controller),
on_intermediate_ratio_callback_(
std::move(on_intermediate_ratio_callback)) {
controller_->AddObserver(this);
}
IntermediateShownRatioWaiter(const IntermediateShownRatioWaiter&) = delete;
IntermediateShownRatioWaiter& operator=(const IntermediateShownRatioWaiter&) =
delete;
~IntermediateShownRatioWaiter() override {
controller_->RemoveObserver(this);
}
bool seen_intermediate_ratios() const { return seen_intermediate_ratios_; }
// TestControllerObserver:
void OnShownRatioChanged(float shown_ratio) override {
seen_intermediate_ratios_ |= shown_ratio > 0.0 && shown_ratio < 1.f;
if (!seen_intermediate_ratios_)
return;
if (on_intermediate_ratio_callback_)
std::move(on_intermediate_ratio_callback_).Run();
if (run_loop_)
run_loop_->Quit();
}
void OnGestureScrollInProgressChanged(bool in_progress) override {}
void Wait() {
if (seen_intermediate_ratios_)
return;
run_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
run_loop_->Run();
}
private:
TestController* controller_;
std::unique_ptr<base::RunLoop> run_loop_;
base::OnceClosure on_intermediate_ratio_callback_;
bool seen_intermediate_ratios_ = false;
};
// TODO(crbug.com/1055958): Test is flaky.
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
DISABLED_TestIntermediateSliding) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Navigate to our test page that has a long vertical content which we can use
// to test page scrolling.
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
content::WebContents* active_contents =
browser_view()->GetActiveWebContents();
PageStateUpdateWaiter page_state_update_waiter(active_contents);
page_state_update_waiter.Wait();
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow(),
browser_window);
const gfx::Point start_point = event_generator.current_screen_location();
const gfx::Point end_point = start_point + gfx::Vector2d(0, -100);
LayoutTestView layout_test_view{browser_view()};
{
// We will start scrolling while top-chrome is fully shown, in which case
// the `DoBrowserControlsShrinkRendererSize` bit is true ...
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
// ... It should change to false at the beginning of sliding and remain
// false while sliding is in progress.
const bool expected_shrink_renderer_size = false;
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
IntermediateShownRatioWaiter fractional_ratio_waiter(
top_controls_slide_controller(),
base::BindOnce(
&TopControlsSlideControllerTest::CheckIntermediateScrollStep,
base::Unretained(this), &layout_test_view,
expected_shrink_renderer_size));
layout_test_view.Reset();
event_generator.PressTouch();
SynchronizeBrowserWithRenderer(active_contents);
for (gfx::Point current_point = start_point;
!(current_point == end_point ||
fractional_ratio_waiter.seen_intermediate_ratios());
current_point += gfx::Vector2d(0, -1)) {
event_generator.MoveTouch(current_point);
SynchronizeBrowserWithRenderer(active_contents);
}
event_generator.MoveTouch(end_point);
event_generator.ReleaseTouch();
waiter.WaitForRatio(0.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 0);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyHidden);
// Now that sliding ended, and top-chrome is fully hidden, the
// `DoBrowserControlsShrinkRendererSize` bit should remain false ...
EXPECT_FALSE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
}
SynchronizeBrowserWithRenderer(active_contents);
{
// ... and when scrolling in the other direction towards a fully shown
// top-chrome, it should remain false while sliding is in progress.
const bool expected_shrink_renderer_size = false;
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
IntermediateShownRatioWaiter fractional_ratio_waiter(
top_controls_slide_controller(),
base::BindOnce(
&TopControlsSlideControllerTest::CheckIntermediateScrollStep,
base::Unretained(this), &layout_test_view,
expected_shrink_renderer_size));
layout_test_view.Reset();
event_generator.PressTouch();
SynchronizeBrowserWithRenderer(active_contents);
for (gfx::Point current_point = end_point;
!(current_point == start_point ||
fractional_ratio_waiter.seen_intermediate_ratios());
current_point += gfx::Vector2d(0, 1)) {
event_generator.MoveTouch(current_point);
SynchronizeBrowserWithRenderer(active_contents);
}
event_generator.MoveTouch(start_point);
event_generator.ReleaseTouch();
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
// Now that sliding ended, and top-chrome is fully shown, the
// `DoBrowserControlsShrinkRendererSize` bit should be true.
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
}
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
DisplayMetricsChangeWhileInProgress) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
// Triggers a display metrics change event while both gesture scrolling and
// sliding are in progress.
auto rotate_display_while_in_progress =
[](content::WebContents* contents, TestController* slide_controller,
ui::test::EventGenerator* generator) {
ASSERT_TRUE(slide_controller->IsTopControlsGestureScrollInProgress());
ASSERT_TRUE(slide_controller->IsTopControlsSlidingInProgress());
// Trigger the keyboard shrotcut for changing the device scale factor.
// This should result in a display metric change.
constexpr int kFlags = ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN;
generator->PressKey(ui::VKEY_OEM_PLUS, kFlags);
generator->ReleaseKey(ui::VKEY_OEM_PLUS, kFlags);
// Test that as result of the above, sliding has been temporarily
// disabled, and that the top controls are fully shown.
EXPECT_FALSE(slide_controller->IsEnabled());
EXPECT_FLOAT_EQ(slide_controller->GetShownRatio(), 1.f);
// Even though gesture scrolling hasn't ended.
EXPECT_TRUE(slide_controller->IsTopControlsGestureScrollInProgress());
};
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow(),
browser_window);
const gfx::Point start_point = event_generator.current_screen_location();
const gfx::Point end_point = start_point + gfx::Vector2d(0, -100);
auto* active_contents = browser_view()->GetActiveWebContents();
IntermediateShownRatioWaiter fractional_ratio_waiter(
top_controls_slide_controller(),
base::BindOnce(rotate_display_while_in_progress, active_contents,
top_controls_slide_controller(), &event_generator));
event_generator.set_current_screen_location(start_point);
event_generator.PressTouch();
SynchronizeBrowserWithRenderer(active_contents);
for (gfx::Point current_point = start_point;
!(current_point == end_point ||
fractional_ratio_waiter.seen_intermediate_ratios());
current_point += gfx::Vector2d(0, -1)) {
event_generator.MoveTouch(current_point);
SynchronizeBrowserWithRenderer(active_contents);
}
// Release touch and wait for gesture scrolling to end.
GestureScrollInProgressChangeWaiter waiter{top_controls_slide_controller()};
event_generator.ReleaseTouch();
waiter.WaitForInProgressState(false);
// Expect that sliding has been re-enabled, and the top controls are still
// fully shown.
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
}
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, TestPermissionBubble) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
const GURL url(embedded_test_server()->GetURL("/top_controls_scroll.html"));
OpenUrlAtIndex(url, 0);
content::WebContents* active_contents =
browser_view()->GetActiveWebContents();
PageStateUpdateWaiter page_state_update_waiter(active_contents);
page_state_update_waiter.Wait();
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
// Hide top chrome.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// Fire a geolocation permission request, which should show a permission
// request bubble resulting in top chrome unhiding.
auto decided = [](ContentSetting, bool) {};
permissions::PermissionRequest permission_request(
url, permissions::RequestType::kGeolocation, true /* user_gesture */,
base::BindOnce(decided), base::DoNothing() /* delete_callback */);
auto* permission_manager =
permissions::PermissionRequestManager::FromWebContents(active_contents);
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
permission_manager->AddRequest(active_contents->GetMainFrame(),
&permission_request);
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
EXPECT_TRUE(permission_manager->IsRequestInProgress());
// It shouldn't be possible to hide top-chrome as long as the bubble is
// visible.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyShown);
// Dismiss the bubble.
permission_manager->Dismiss();
EXPECT_FALSE(permission_manager->IsRequestInProgress());
SynchronizeBrowserWithRenderer(active_contents);
// Now it is possible to hide top-chrome again.
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
}
// Flaky on ChromeOS Release bots. https://crbug.com/1033648
#if defined(OS_CHROMEOS) && defined(NDEBUG)
#define MAYBE_TestToggleChromeVox DISABLED_TestToggleChromeVox
#else
#define MAYBE_TestToggleChromeVox TestToggleChromeVox
#endif
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
MAYBE_TestToggleChromeVox) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
OpenUrlAtIndex(embedded_test_server()->GetURL("/top_controls_scroll.html"),
0);
content::WebContents* active_contents =
browser_view()->GetActiveWebContents();
PageStateUpdateWaiter page_state_update_waiter(active_contents);
page_state_update_waiter.Wait();
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
// Enable Chromevox (spoken feedback) and expect that top-chrome will be fully
// shown, and sliding top-chrome is no longer enabled.
TopControlsShownRatioWaiter waiter(top_controls_slide_controller());
AccessibilityManager::Get()->EnableSpokenFeedback(true);
SynchronizeBrowserWithRenderer(active_contents);
EXPECT_TRUE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
waiter.WaitForRatio(1.f);
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
EXPECT_TRUE(
browser_view()->DoBrowserControlsShrinkRendererSize(active_contents));
// Now disable Chromevox, and expect it's now possible to hide top-chrome with
// gesture scrolling.
AccessibilityManager::Get()->EnableSpokenFeedback(false);
SynchronizeBrowserWithRenderer(active_contents);
EXPECT_FALSE(AccessibilityManager::Get()->IsSpokenFeedbackEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown);
ScrollAndExpectTopChromeToBe(ScrollDirection::kDown,
TopChromeShownState::kFullyHidden);
}
// Regression test for https://crbug.com/1163276.
// TODO(crbug.com/1190997): Test times out flakily.
IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest,
DISABLED_NoCrashOnNewTabWhileScrolling) {
ToggleTabletMode();
ASSERT_TRUE(GetTabletModeEnabled());
EXPECT_TRUE(top_controls_slide_controller()->IsEnabled());
EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f);
NavigateActiveTabToUrl(
embedded_test_server()->GetURL("/top_controls_scroll.html"));
ASSERT_EQ(browser()->tab_strip_model()->count(), 1);
aura::Window* browser_window = browser()->window()->GetNativeWindow();
ui::test::EventGenerator event_generator(browser_window->GetRootWindow(),
browser_window);
const gfx::Point start_point = event_generator.current_screen_location();
auto* active_contents = browser_view()->GetActiveWebContents();
// Create a new tab while gesture scrolling is in progress, and top-chrome is
// fully hidden.
event_generator.PressTouch();
SynchronizeBrowserWithRenderer(active_contents);
auto current_point = start_point;
while (!(
top_controls_slide_controller()->IsTopControlsGestureScrollInProgress() &&
top_controls_slide_controller()->GetShownRatio() == 0.f)) {
current_point += gfx::Vector2d(0, -1);
event_generator.MoveTouch(current_point);
SynchronizeBrowserWithRenderer(active_contents);
}
constexpr int kFlags = ui::EF_CONTROL_DOWN;
event_generator.PressKey(ui::VKEY_T, kFlags);
event_generator.ReleaseKey(ui::VKEY_T, kFlags);
event_generator.ReleaseTouch();
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
}
// TODO(crbug.com/989131): Add test coverage that covers using WebUITabStrip.
} // namespace