chromium/chrome/browser/ui/ash/back_gesture/back_gesture_browsertest.cc

// 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/test/shell_test_api.h"
#include "ash/wm/tablet_mode/tablet_mode_controller_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/browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "ui/aura/window.h"
#include "ui/display/screen.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::kUnknown;
    }
  }

  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::kUnknown;
    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::kUnknown;
  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_SRC_TEST_DATA_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::TabletModeControllerTestApi().EnterTabletMode();
    ASSERT_TRUE(display::Screen::GetScreen()->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::kGestureEnd);

  // BackGestureEventHandler did not handle gesture scroll events, so |recorder|
  // should be able to get the scroll events.
  EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::kGestureTapDown));
  EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::kGestureScrollBegin));
  EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::kGestureScrollUpdate));
  EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::kGestureScrollEnd) ||
              recorder.HasReceivedEvent(ui::EventType::kScrollFlingStart));
  // 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::kKeyPressed));
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kKeyReleased));

  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::kGestureEnd);

  // BackGestureEventHandler has handled gesture scroll events, so |recorder|
  // should not be able to get the scroll events.
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kGestureTapDown));
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kGestureScrollBegin));
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kGestureScrollUpdate));
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kGestureScrollEnd));
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kScrollFlingStart));
  // ui::VKEY_BROWSR_BACK key pressed and key released will be generated because
  // back operation can be performed.
  EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::kKeyPressed));
  EXPECT_TRUE(recorder.HasReceivedEvent(ui::EventType::kKeyReleased));
  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::kGestureEnd);

  // 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::kKeyPressed));
  EXPECT_FALSE(recorder.HasReceivedEvent(ui::EventType::kKeyReleased));
  browser_window->RemovePreTargetHandler(&recorder);
}