| // 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 <vector> |
| |
| #include "ash/public/cpp/ash_switches.h" |
| #include "ash/public/interfaces/constants.mojom.h" |
| #include "ash/public/interfaces/cros_display_config.mojom-test-utils.h" |
| #include "ash/public/interfaces/cros_display_config.mojom.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/path_service.h" |
| #include "base/strings/safe_sprintf.h" |
| #include "cc/base/math_util.h" |
| #include "chrome/browser/chromeos/accessibility/accessibility_manager.h" |
| #include "chrome/browser/permissions/permission_request_impl.h" |
| #include "chrome/browser/permissions/permission_request_manager.h" |
| #include "chrome/browser/ui/ash/tablet_mode_client.h" |
| #include "chrome/browser/ui/browser_commands.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/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 "content/public/common/service_manager_connection.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "services/service_manager/public/cpp/connector.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/display.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/views/controls/native/native_view_host.h" |
| #include "ui/views/view_observer.h" |
| |
| namespace { |
| |
| enum class TopChromeShownState { |
| kFullyShown, |
| kFullyHidden, |
| }; |
| |
| enum class ScrollDirection { |
| kUp, |
| kDown, |
| }; |
| |
| // 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); |
| generator->GestureScrollSequenceWithCallback( |
| start_point, end_point, |
| generator->CalculateScrollDurationForFlingVelocity( |
| start_point, end_point, 100 /* velocity */, 2 /* steps */), |
| 2 /* steps */, |
| base::BindRepeating([](ui::EventType, const gfx::Vector2dF&) { |
| // Give the event a chance to propagate to renderer before sending the |
| // next one. |
| base::RunLoop().RunUntilIdle(); |
| })); |
| } |
| |
| // 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()); |
| } |
| |
| // Waits for the first non-empty paint for a given WebContents. To be able to |
| // test gesture scroll events reliably, we must wait until the tab is fully |
| // painted. Otherwise, the events will be ignored. |
| class TabNonEmptyPaintWaiter : public content::WebContentsObserver { |
| public: |
| explicit TabNonEmptyPaintWaiter(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) {} |
| |
| ~TabNonEmptyPaintWaiter() override = default; |
| |
| void Wait() { |
| if (web_contents()->CompletedFirstVisuallyNonEmptyPaint()) |
| return; |
| |
| run_loop_.Run(); |
| } |
| |
| private: |
| // content::WebContentsObserver: |
| void DidFirstVisuallyNonEmptyPaint() override { run_loop_.Quit(); } |
| |
| base::RunLoop run_loop_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TabNonEmptyPaintWaiter); |
| }; |
| |
| 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() 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(); |
| } |
| |
| private: |
| std::unique_ptr<TopControlsSlideController> real_controller_; |
| |
| base::ObserverList<TestControllerObserver>::Unchecked observers_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestController); |
| }; |
| |
| // 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() 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; |
| |
| DISALLOW_COPY_AND_ASSIGN(TopControlsShownRatioWaiter); |
| }; |
| |
| } // namespace |
| |
| class TopControlsSlideControllerTest : public InProcessBrowserTest { |
| public: |
| TopControlsSlideControllerTest() = default; |
| ~TopControlsSlideControllerTest() override = default; |
| |
| BrowserView* browser_view() const { |
| return BrowserView::GetBrowserViewForBrowser(browser()); |
| } |
| |
| TestController* top_controls_slide_controller() const { |
| DCHECK(test_controller_); |
| return test_controller_; |
| } |
| |
| void SetUpDefaultCommandLine(base::CommandLine* command_line) override { |
| InProcessBrowserTest::SetUpDefaultCommandLine(command_line); |
| |
| command_line->AppendSwitch(ash::switches::kAshEnableTabletMode); |
| } |
| |
| 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(0, url, ui::PAGE_TRANSITION_TYPED); |
| TabNonEmptyPaintWaiter waiter( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| waiter.Wait(); |
| } |
| |
| void ToggleTabletMode() { |
| auto* tablet_mode_client = TabletModeClient::Get(); |
| tablet_mode_client->OnTabletModeToggled( |
| !tablet_mode_client->tablet_mode_enabled()); |
| } |
| |
| bool GetTabletModeEnabled() const { |
| return TabletModeClient::Get()->tablet_mode_enabled(); |
| } |
| |
| 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. |
| gfx::Transform expected_transform; |
| DCHECK( |
| browser_view->contents_web_view()->holder()->GetNativeViewContainer()); |
| ui::Layer* contents_container_layer = browser_view->contents_web_view() |
| ->holder() |
| ->GetNativeViewContainer() |
| ->layer(); |
| ASSERT_TRUE(contents_container_layer); |
| CompareTranslations(expected_transform, |
| contents_container_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 |expected_shrink_renderer_size| will be checked against the |
| // `DoBrowserControlsShrinkRendererSize` bit while sliding. |
| void CheckIntermediateScrollStep(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); |
| |
| 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); |
| |
| ASSERT_TRUE(browser_view() |
| ->contents_web_view() |
| ->holder() |
| ->GetNativeViewContainer()); |
| ui::Layer* contents_container_layer = browser_view() |
| ->contents_web_view() |
| ->holder() |
| ->GetNativeViewContainer() |
| ->layer(); |
| ASSERT_TRUE(contents_container_layer); |
| CompareTranslations(expected_transform, |
| contents_container_layer->transform()); |
| CompareTranslations(expected_transform, root_view_layer->transform()); |
| } |
| |
| // 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 = event_generator.current_screen_location(); |
| 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); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| |
| TestController* test_controller_ = nullptr; // Not owned. |
| |
| DISALLOW_COPY_AND_ASSIGN(TopControlsSlideControllerTest); |
| }; |
| |
| 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 = new Browser(params); |
| AddBlankTabAndShow(browser); |
| |
| ASSERT_TRUE(browser->is_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); |
| } |
| |
| // TODO(https://crbug.com/911949): Times out on CrOS on the waterfall. |
| IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, |
| DISABLED_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. |
| 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)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, 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. |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("/top_controls_scroll.html")); |
| TabNonEmptyPaintWaiter paint_waiter( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| paint_waiter.Wait(); |
| 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); |
| |
| 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. |
| 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. |
| 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; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| contents, get_js_function_body(false /* should_focus */), &bool_result)); |
| EXPECT_TRUE(bool_result); |
| 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() 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BrowserViewLayoutWaiter); |
| }; |
| |
| 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<display::Display::Rotation> rotations_to_try = { |
| display::Display::ROTATE_90, display::Display::ROTATE_180, |
| display::Display::ROTATE_270, display::Display::ROTATE_0, |
| }; |
| |
| ash::mojom::CrosDisplayConfigControllerPtr cros_display_config; |
| content::ServiceManagerConnection::GetForProcess() |
| ->GetConnector() |
| ->BindInterface(ash::mojom::kServiceName, &cros_display_config); |
| 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() override = default; |
| |
| void Wait() { run_loop_.Run(); } |
| |
| // 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_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PageStateUpdateWaiter); |
| }; |
| |
| // 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); |
| |
| // On mash, use nullptr root windows to route events over mojo to ash. |
| aura::Window* browser_window = browser()->window()->GetNativeWindow(); |
| ui::test::EventGenerator event_generator( |
| features::IsUsingWindowService() ? nullptr |
| : browser_window->GetRootWindow()); |
| |
| // 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); |
| event_generator.MoveMouseTo(54, 173); |
| event_generator.ClickLeftButton(); |
| page_state_update_waiter.Wait(); |
| |
| // Verify that the element has been focused. |
| EXPECT_EQ(true, content::EvalJs(contents, "selectFocused;")); |
| |
| // Now send a mouse click event that should select the forth option in the |
| // drop-down menu. |
| event_generator.MoveMouseTo(54, 300); |
| event_generator.ClickLeftButton(); |
| |
| // Evaluate an empty sentence to make sure that the event processing is done |
| // in the content. |
| ignore_result(content::EvalJs(contents, ";")); |
| |
| // Verify that the selected option has changed and the forth 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. |
| ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("/top_controls_scroll.html")); |
| content::WebContents* active_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| TabNonEmptyPaintWaiter paint_waiter(active_contents); |
| paint_waiter.Wait(); |
| 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() override { |
| controller_->RemoveObserver(this); |
| } |
| |
| // 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; |
| |
| DISALLOW_COPY_AND_ASSIGN(IntermediateShownRatioWaiter); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, |
| 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); |
| |
| // Large number of ET_GESTURE_SCROLL_UPDATE steps with small velocity so that |
| // we can see fractional shown ratios. |
| const int scroll_steps = 1000; |
| const base::TimeDelta scroll_step_delay = |
| event_generator.CalculateScrollDurationForFlingVelocity( |
| start_point, end_point, 2 /* velocity */, scroll_steps); |
| |
| { |
| // 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), expected_shrink_renderer_size)); |
| event_generator.GestureScrollSequence(start_point, end_point, |
| scroll_step_delay, scroll_steps); |
| fractional_ratio_waiter.Wait(); |
| 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)); |
| } |
| |
| content::WaitForResizeComplete(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), expected_shrink_renderer_size)); |
| |
| event_generator.GestureScrollSequence(end_point, start_point, |
| scroll_step_delay, scroll_steps); |
| fractional_ratio_waiter.Wait(); |
| 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, 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) {}; |
| PermissionRequestImpl permission_request( |
| url, CONTENT_SETTINGS_TYPE_GEOLOCATION, true /* user_gesture */, |
| base::BindRepeating(decided), base::DoNothing() /* delete_callback */); |
| auto* permission_manager = |
| PermissionRequestManager::FromWebContents(active_contents); |
| TopControlsShownRatioWaiter waiter(top_controls_slide_controller()); |
| permission_manager->AddRequest(&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->IsBubbleVisible()); |
| |
| // It shouldn't be possible to hide top-chrome as long as the bubble is |
| // visible. |
| ScrollAndExpectTopChromeToBe(ScrollDirection::kDown, |
| TopChromeShownState::kFullyShown); |
| |
| // Dismiss the bubble. |
| EXPECT_TRUE(permission_manager->GetBubbleWindow()); |
| views::Widget::GetWidgetForNativeView(permission_manager->GetBubbleWindow()) |
| ->CloseNow(); |
| EXPECT_FALSE(permission_manager->IsBubbleVisible()); |
| content::WaitForResizeComplete(active_contents); |
| |
| // Now it is possible to hide top-chrome again. |
| ScrollAndExpectTopChromeToBe(ScrollDirection::kDown, |
| TopChromeShownState::kFullyHidden); |
| } |
| |
| // Waits for a compositor frame to be drawn and committed on the given |
| // web_contents. |
| class CompositorFrameWaiter : content::WebContentsObserver { |
| public: |
| explicit CompositorFrameWaiter(content::WebContents* contents) |
| : WebContentsObserver(contents) {} |
| ~CompositorFrameWaiter() override = default; |
| |
| void Wait() { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| run_loop_.reset(); |
| } |
| |
| // content::WebContentsObserver: |
| void DidCommitAndDrawCompositorFrame() override { |
| if (run_loop_) |
| run_loop_->Quit(); |
| } |
| |
| private: |
| std::unique_ptr<base::RunLoop> run_loop_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CompositorFrameWaiter); |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(TopControlsSlideControllerTest, 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()); |
| chromeos::AccessibilityManager::Get()->EnableSpokenFeedback(true); |
| EXPECT_TRUE(chromeos::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. |
| CompositorFrameWaiter compositor_frame_waiter(active_contents); |
| chromeos::AccessibilityManager::Get()->EnableSpokenFeedback(false); |
| compositor_frame_waiter.Wait(); |
| content::WaitForResizeComplete(active_contents); |
| EXPECT_FALSE( |
| chromeos::AccessibilityManager::Get()->IsSpokenFeedbackEnabled()); |
| EXPECT_FLOAT_EQ(top_controls_slide_controller()->GetShownRatio(), 1.f); |
| CheckBrowserLayout(browser_view(), TopChromeShownState::kFullyShown); |
| |
| ScrollAndExpectTopChromeToBe(ScrollDirection::kDown, |
| TopChromeShownState::kFullyHidden); |
| } |
| |
| } // namespace |