| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/public/cpp/tablet_mode.h" |
| #include "ash/public/cpp/test/shell_test_api.h" |
| #include "base/containers/contains.h" |
| #include "base/path_service.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "content/public/browser/render_widget_host.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_base.h" |
| #include "content/public/test/hit_test_region_observer.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "ui/aura/window.h" |
| #include "ui/base/test/ui_controls_aura.h" |
| #include "ui/events/test/event_generator.h" |
| #include "ui/events/types/event_type.h" |
| |
| namespace { |
| |
| // The class to record received gesture events and key events to verify if back |
| // gesture has been performed. |
| class BackGestureEventRecorder : public ui::EventHandler { |
| public: |
| BackGestureEventRecorder() = default; |
| BackGestureEventRecorder(const BackGestureEventRecorder&) = delete; |
| BackGestureEventRecorder& operator=(const BackGestureEventRecorder&) = delete; |
| ~BackGestureEventRecorder() override = default; |
| |
| // ui::EventHandler: |
| void OnGestureEvent(ui::GestureEvent* event) override { |
| received_event_types_.insert(event->type()); |
| if (wait_for_event_ == event->type() && run_loop_) { |
| run_loop_->Quit(); |
| wait_for_event_ = ui::EventType::ET_UNKNOWN; |
| } |
| } |
| |
| void OnKeyEvent(ui::KeyEvent* event) override { |
| // If back gesture can be performed, a ui::VKEY_BROWSR_BACK key pressed and |
| // key released will be generated. |
| received_event_types_.insert(event->type()); |
| } |
| |
| void WaitUntilReceivedGestureEvent(ui::EventType event_type) { |
| wait_for_event_ = event_type; |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| } |
| |
| bool HasReceivedEvent(ui::EventType event_type) { |
| return base::Contains(received_event_types_, event_type); |
| } |
| |
| void Reset() { |
| received_event_types_.clear(); |
| wait_for_event_ = ui::EventType::ET_UNKNOWN; |
| if (run_loop_) |
| run_loop_->Quit(); |
| } |
| |
| private: |
| // Stores the event types of the received gesture events. |
| base::flat_set<ui::EventType> received_event_types_; |
| ui::EventType wait_for_event_ = ui::EventType::ET_UNKNOWN; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| }; |
| |
| } // namespace |
| |
| class BackGestureBrowserTest : public InProcessBrowserTest { |
| public: |
| BackGestureBrowserTest() = default; |
| BackGestureBrowserTest(const BackGestureBrowserTest&) = delete; |
| BackGestureBrowserTest& operator=(const BackGestureBrowserTest&) = delete; |
| ~BackGestureBrowserTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| 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/ash/back_gesture")); |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| |
| // Enter tablet mode. |
| ash::ShellTestApi().SetTabletModeEnabledForTest(true); |
| ASSERT_TRUE(ash::TabletMode::Get()->InTabletMode()); |
| } |
| |
| content::RenderWidgetHost* GetRenderWidgetHost() { |
| return browser() |
| ->tab_strip_model() |
| ->GetActiveWebContents() |
| ->GetRenderWidgetHostView() |
| ->GetRenderWidgetHost(); |
| } |
| |
| // Navigate to |url| and wait until browser thread is synchronized with render |
| // thread. It's needed so that the touch action is correctly initialized. |
| void NavigateToURLAndWaitForMainFrame(const GURL& url) { |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); |
| content::MainThreadFrameObserver frame_observer(GetRenderWidgetHost()); |
| frame_observer.Wait(); |
| } |
| }; |
| |
| // Test back gesture behavior with different touch actions. |
| IN_PROC_BROWSER_TEST_F(BackGestureBrowserTest, TouchActions) { |
| // Navigate to a page with {touch-action: none} defined. |
| NavigateToURLAndWaitForMainFrame( |
| embedded_test_server()->GetURL("/page_touch_action_none.html")); |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); |
| |
| aura::Window* browser_window = browser()->window()->GetNativeWindow(); |
| const gfx::Rect bounds = browser()->window()->GetBounds(); |
| const gfx::Point start_point = bounds.left_center(); |
| const gfx::Point end_point = |
| gfx::Point(start_point.x() + 200, start_point.y()); |
| |
| BackGestureEventRecorder recorder; |
| browser_window->AddPreTargetHandler(&recorder); |
| |
| ui::test::EventGenerator event_generator(browser_window->GetRootWindow(), |
| browser_window); |
| event_generator.set_current_screen_location(start_point); |
| event_generator.PressTouch(); |
| event_generator.MoveTouch(end_point); |
| event_generator.ReleaseTouch(); |
| recorder.WaitUntilReceivedGestureEvent(ui::EventType::ET_GESTURE_END); |
| |
| // BackGestureEventHandler did not handle gesture scroll events, so |recorder| |
| // should be able to get the scroll events. |
| EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_TAP_DOWN)); |
| EXPECT_TRUE( |
| recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_SCROLL_BEGIN)); |
| EXPECT_TRUE( |
| recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_SCROLL_UPDATE)); |
| EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_SCROLL_END) || |
| recorder.HasReceivedEvent(ui::EventType::ET_SCROLL_FLING_START)); |
| // ui::VKEY_BROWSR_BACK key pressed and key released will not be generated |
| // because back operation is not performed. |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_KEY_PRESSED)); |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_KEY_RELEASED)); |
| |
| recorder.Reset(); |
| |
| // Now navigate to a page with {touch-action: auto} defined. |
| NavigateToURLAndWaitForMainFrame( |
| embedded_test_server()->GetURL("/page_touch_action_auto.html")); |
| |
| event_generator.set_current_screen_location(start_point); |
| event_generator.PressTouch(); |
| event_generator.MoveTouch(end_point); |
| event_generator.ReleaseTouch(); |
| recorder.WaitUntilReceivedGestureEvent(ui::EventType::ET_GESTURE_END); |
| |
| // BackGestureEventHandler has handled gesture scroll events, so |recorder| |
| // should not be able to get the scroll events. |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_TAP_DOWN)); |
| EXPECT_FALSE( |
| recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_SCROLL_BEGIN)); |
| EXPECT_FALSE( |
| recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_SCROLL_UPDATE)); |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_GESTURE_SCROLL_END)); |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_SCROLL_FLING_START)); |
| // ui::VKEY_BROWSR_BACK key pressed and key released will be generated because |
| // back operation can be performed. |
| EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::ET_KEY_PRESSED)); |
| EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::ET_KEY_RELEASED)); |
| browser_window->RemovePreTargetHandler(&recorder); |
| } |
| |
| // Test the back gesture behavior on page that has prevented default touch |
| // behavior. |
| IN_PROC_BROWSER_TEST_F(BackGestureBrowserTest, PreventDefault) { |
| NavigateToURLAndWaitForMainFrame( |
| embedded_test_server()->GetURL("/page_prevent_default.html")); |
| ASSERT_EQ(browser()->tab_strip_model()->count(), 1); |
| |
| aura::Window* browser_window = browser()->window()->GetNativeWindow(); |
| const gfx::Rect bounds = browser()->window()->GetBounds(); |
| BackGestureEventRecorder recorder; |
| browser_window->AddPreTargetHandler(&recorder); |
| |
| // Start drag on the page that prevents default touch behavior. |
| const gfx::Point start_point = bounds.left_center(); |
| const gfx::Point end_point = |
| gfx::Point(start_point.x() + 200, start_point.y()); |
| ui::test::EventGenerator event_generator(browser_window->GetRootWindow(), |
| browser_window); |
| event_generator.set_current_screen_location(start_point); |
| event_generator.PressTouch(); |
| event_generator.MoveTouch(end_point); |
| event_generator.ReleaseTouch(); |
| recorder.WaitUntilReceivedGestureEvent(ui::EventType::ET_GESTURE_END); |
| |
| // ui::VKEY_BROWSR_BACK key pressed and key released will not be generated |
| // because back operation is not performed. |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_KEY_PRESSED)); |
| EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::ET_KEY_RELEASED)); |
| browser_window->RemovePreTargetHandler(&recorder); |
| } |