chromium/chrome/browser/ui/views/drag_and_drop_interactive_uitest.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <initializer_list>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/ranges/algorithm.h"
#include "base/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/pattern.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/run_until.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/view_ids.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/omnibox/omnibox_view_views.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "content/public/test/test_frame_navigation_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "net/base/filename_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/client/drag_drop_delegate.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window.h"
#include "ui/base/dragdrop/drag_drop_types.h"
#include "ui/base/dragdrop/drop_target_event.h"
#include "ui/base/dragdrop/mojom/drag_drop_types.mojom.h"
#include "ui/base/dragdrop/os_exchange_data.h"
#include "ui/compositor/layer_tree_owner.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace chrome {
namespace {

DragOperation;

// TODO(lukasza): Support testing on non-Aura platforms (i.e. Android + Mac?).
//
// Notes for the TODO above:
//
// - Why inject/simulate drag-and-drop events at the aura::Window* level.
//
//   - It seems better to inject into UI libraries to cover code *inside* these
//     libraries.  This might complicate simulation a little bit (i.e. picking
//     the right aura::Window and/or aura::client::DragDropDelegate to target),
//     but otherwise important bits of code wouldn't get test coverage (i.e.
//     directly injecting into RenderViewHost->DragTargetDragEnter seems wrong).
//
//   - In theory, we could introduce WebContentsImpl::DragTargetDragEnter (to be
//     used by all UI platforms - so reused by web_contents_view_android.cc,
//     web_contents_view_aura.cc, web_drag_dest_mac.mm), but it feels wrong - UI
//     libraries should already know which widget is the target of the event and
//     so should be able to talk directly to the right widget (i.e. WebContents
//     should not be responsible for mapping coordinates to a widget - this is
//     the job of the UI library).
//
// - Unknowns:
//
//   - Will this work for WebView and Plugin testing.

// Test helper for simulating drag and drop happening in WebContents.
class DragAndDropSimulator {};

// Helper for waiting until a drag-and-drop starts (e.g. in response to a
// mouse-down + mouse-move simulated by the test).
class DragStartWaiter : public aura::client::DragDropClient {};

// Helper for waiting for notifications from
// content/test/data/drag_and_drop/event_monitoring.js
class DOMDragEventWaiter {};

// Helper for verifying contents of DOM events associated with drag-and-drop.
class DOMDragEventVerifier {};

// Helper for monitoring event notifications from
// content/test/data/drag_and_drop/event_monitoring.js
// and counting how many events of a given type were received.
class DOMDragEventCounter {};

const char kTestPagePath[] =;

// bool use_cross_site_subframe, double device_scale_factor.
TestParam;

}  // namespace

// Note: Tests that require OS events need to be added to
// ozone-linux.interactive_ui_tests_wayland.filter.
class DragAndDropBrowserTest : public InProcessBrowserTest,
                               public testing::WithParamInterface<TestParam> {};

// Scenario: drag text from outside the browser and drop to the right frame.
// Test coverage: dragover, drop DOM events.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DropTextFromOutside) {}

// Scenario: drag URL from outside the browser and drop to the right frame
// (e.g. this is similar to a drag that starts from the bookmarks bar, except
// that here there is no drag start event - as-if the drag was started in
// another application).
//
// This test mostly focuses on covering 1) the navigation path, 2) focus
// behavior.  This test explicitly does not cover the dragover and/or drop DOM
// events - they are already covered via the DropTextFromOutside test above.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DropValidUrlFromOutside) {}

// Scenario: drag a URL into the Omnibox.  This is a regression test for
// https://crbug.com/670123.
// TODO(crbug.com/344168586): Very flaky on linux-chromeos-rel bots.
#if BUILDFLAG(IS_CHROMEOS_ASH) && defined(NDEBUG)
#define MAYBE_DropUrlIntoOmnibox
#else
#define MAYBE_DropUrlIntoOmnibox
#endif
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, MAYBE_DropUrlIntoOmnibox) {}

// Scenario: drag a file from outside the browser and drop to the right frame
// (e.g. starting a drag in a separate file explorer application, like Nemo on
// gLinux).
//
// This test mostly focuses on covering 1) the navigation path, 2) focus
// behavior.  This test explicitly does not cover the dragover and/or drop DOM
// events - they are already covered via the DropTextFromOutside test above.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DropFileFromOutside) {}

// Scenario: drag URL from outside the browser and drop to the right frame.
// Mostly focuses on covering the navigation path (the dragover and/or drop DOM
// events are already covered via the DropTextFromOutside test above).
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DropForbiddenUrlFromOutside) {}

// Scenario: starting a drag in left frame
// Test coverage: dragstart DOM event, dragstart data passed to the OS.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DragStartInFrame) {}

#if BUILDFLAG(IS_WIN)
// There is no known way to execute test-controlled tasks during
// a drag-and-drop loop run by Windows OS.
#define MAYBE_DragSameOriginImageBetweenFrames
#elif BUILDFLAG(IS_LINUX)
// Failing to receive final drop event on linux crbug.com/1268407.
#define MAYBE_DragSameOriginImageBetweenFrames
#else
#define MAYBE_DragSameOriginImageBetweenFrames
#endif

// Data that needs to be shared across multiple test steps below
// (i.e. across DragImageBetweenFrames_Step2 and DragImageBetweenFrames_Step3).
struct DragAndDropBrowserTest::DragImageBetweenFrames_TestState {};

// Scenario: drag a same-origin image from the left into the right frame.
// Test coverage: dragleave, dragenter, dragover, dragend, drop DOM events.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest,
                       MAYBE_DragSameOriginImageBetweenFrames) {}

#if BUILDFLAG(IS_WIN)
#define MAYBE_DragCorsSameOriginImageBetweenFrames
#elif BUILDFLAG(IS_LINUX)
#define MAYBE_DragCorsSameOriginImageBetweenFrames
#else
#define MAYBE_DragCorsSameOriginImageBetweenFrames
#endif

// Scenario: drag a CORS same-orign (different origin with `<img crossorigin>`
// attribute, and `Access-Control-Allow-Origin: *` header) image from the left
// into the right frame. Image should be accessible.
// Test coverage: dragleave, dragenter, dragover, dragend, drop DOM events.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest,
                       MAYBE_DragCorsSameOriginImageBetweenFrames) {}

#if BUILDFLAG(IS_WIN)
#define MAYBE_DragCrossOriginImageBetweenFrames
#elif BUILDFLAG(IS_LINUX)
#define MAYBE_DragCrossOriginImageBetweenFrames
#else
#define MAYBE_DragCrossOriginImageBetweenFrames
#endif

// Scenario: drag a cross-orign image from the left into the right frame. Image
// should not be accessible to the drag/drop events.
// Test coverage: dragleave, dragenter, dragover, dragend, drop DOM events.
// Regression test for https://crbug.com/1264873.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest,
                       MAYBE_DragCrossOriginImageBetweenFrames) {}

void DragAndDropBrowserTest::DragImageBetweenFrames_Start(
    bool image_same_origin,
    bool image_crossorigin_attr) {}

void DragAndDropBrowserTest::DragImageBetweenFrames_Step2(
    DragAndDropBrowserTest::DragImageBetweenFrames_TestState* state) {}

void DragAndDropBrowserTest::DragImageBetweenFrames_Step3(
    DragAndDropBrowserTest::DragImageBetweenFrames_TestState* state) {}

// There is no known way to execute test-controlled tasks during
// a drag-and-drop loop run by Windows OS.
// Also disable the test on Linux due to flaky: crbug.com/1164442
// TODO(crbug.com/40118868): Revisit once build flag switch of lacros-chrome is
// complete.
// TODO(crbug.com/40876472): Enable on ChromeOS ASAN once flakiness is fixed.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || \
    BUILDFLAG(IS_CHROMEOS_LACROS) ||            \
    (BUILDFLAG(IS_CHROMEOS) && defined(ADDRESS_SANITIZER))
#define MAYBE_DragImageFromDisappearingFrame
#else
#define MAYBE_DragImageFromDisappearingFrame
#endif

// Data that needs to be shared across multiple test steps below
// (i.e. across DragImageFromDisappearingFrame_Step2 and
// DragImageFromDisappearingFrame_Step3).
struct DragAndDropBrowserTest::DragImageFromDisappearingFrame_TestState {};

// Scenario: drag an image from the left into the right frame and delete the
// left frame during the drag.  This is a regression test for
// https://crbug.com/670123.
// Test coverage: dragenter, dragover, drop DOM events.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest,
                       MAYBE_DragImageFromDisappearingFrame) {}

void DragAndDropBrowserTest::DragImageFromDisappearingFrame_Step2(
    DragAndDropBrowserTest::DragImageFromDisappearingFrame_TestState* state) {}

void DragAndDropBrowserTest::DragImageFromDisappearingFrame_Step3(
    DragAndDropBrowserTest::DragImageFromDisappearingFrame_TestState* state) {}

// There is no known way to execute test-controlled tasks during
// a drag-and-drop loop run by Windows OS.
// TODO(b:361552512): Flaky on Chrome OS
#if BUILDFLAG(IS_WIN)
#define MAYBE_CrossSiteDrag
#elif BUILDFLAG(IS_CHROMEOS)
#define MAYBE_CrossSiteDrag
#else
#define MAYBE_CrossSiteDrag
#endif

// Data that needs to be shared across multiple test steps below
// (i.e. across CrossSiteDrag_Step2 and CrossSiteDrag_Step3).
struct DragAndDropBrowserTest::CrossSiteDrag_TestState {};

// Scenario: drag an image from the left into the right frame when the
// left-vs-right frames are cross-site.  This is a regression test for
// https://crbug.com/59081.
//
// Test coverage: absence of dragenter, dragover, drop DOM events
// + presence of dragstart, dragleave and dragend.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, MAYBE_CrossSiteDrag) {}

void DragAndDropBrowserTest::CrossSiteDrag_Step2(
    DragAndDropBrowserTest::CrossSiteDrag_TestState* state) {}

void DragAndDropBrowserTest::CrossSiteDrag_Step3(
    DragAndDropBrowserTest::CrossSiteDrag_TestState* state) {}

// There is no known way to execute test-controlled tasks during
// a drag-and-drop loop run by Windows OS.
#if BUILDFLAG(IS_WIN)
#define MAYBE_CrossNavCrossSiteDrag
#else
#define MAYBE_CrossNavCrossSiteDrag
#endif

struct DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState {};

// Scenario: drag from a cross-site frame, navigate the main frame, then drop.
// This is a regression test for https://crbug.com/1485266.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, MAYBE_CrossNavCrossSiteDrag) {}

void DragAndDropBrowserTest::CrossNavCrossSiteDrag_Step2(
    DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState* state) {}

void DragAndDropBrowserTest::CrossNavCrossSiteDrag_Step3(
    DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState* state) {}

// There is no known way to execute test-controlled tasks during
// a drag-and-drop loop run by Windows OS.
#if BUILDFLAG(IS_WIN) || (BUILDFLAG(IS_CHROMEOS_ASH) && \
                          (defined(ADDRESS_SANITIZER) || !defined(NDEBUG)))
// https://crbug.com/1393605: Flaky at ChromeOS ASAN and Debug builds
#define MAYBE_CrossTabDrag
#else
#define MAYBE_CrossTabDrag
#endif

// Data that needs to be shared across multiple test steps below
// (i.e. across CrossTabDrag_Step2 and CrossTabDrag_Step3).
struct DragAndDropBrowserTest::CrossTabDrag_TestState {};

// Scenario: drag an image from one tab to a different cross-site tab. This
// simulates starting a drag, switching tabs as in alt-tab, then completing the
// drop.
// right frames are on different tabs.
// Note that this case is allowed, while the CrossSiteDrag case (cross-site
// same-tab drag) is not allowed.
//
// Test coverage: dragenter, dragover, dragend, drop DOM events.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, MAYBE_CrossTabDrag) {}

void DragAndDropBrowserTest::CrossTabDrag_Step2(
    DragAndDropBrowserTest::CrossTabDrag_TestState* state) {}

void DragAndDropBrowserTest::CrossTabDrag_Step3(
    DragAndDropBrowserTest::CrossTabDrag_TestState* state) {}

// Test that screenX/screenY for drag updates are in screen coordinates.
// See https://crbug.com/600402 where we mistook the root window coordinate
// space for the screen coordinate space.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DragUpdateScreenCoordinates) {}

// TODO(paulmeyer): Should test the case of navigation happening in the middle
// of a drag operation, and cross-site drags should be allowed across a
// navigation.

// Injecting input with scaling works as expected on Chromeos.
// TODO(crbug.com/40231833): Enable tests with a scale factor on lacros.
#if BUILDFLAG(IS_CHROMEOS_ASH)
constexpr std::initializer_list<double> ui_scaling_factors = {1.0, 1.25, 2.0};
#else
// Injecting input with non-1x scaling doesn't work correctly with x11 ozone or
// Windows 7.
constexpr std::initializer_list<double> ui_scaling_factors =;
#endif

INSTANTIATE_TEST_SUITE_P();

INSTANTIATE_TEST_SUITE_P();

#if BUILDFLAG(IS_CHROMEOS_ASH)
class DragAndDropBrowserTestNoParam : public InProcessBrowserTest {
 protected:
  void SimulateDragFromOmniboxToWebContents(base::OnceClosure quit) {
    chrome::FocusLocationBar(browser());

    BrowserView* browser_view =
        BrowserView::GetBrowserViewForBrowser(browser());
    OmniboxViewViews* omnibox_view =
        browser_view->toolbar()->location_bar()->omnibox_view();

    // Simulate mouse move to omnibox.
    gfx::Point point;
    views::View::ConvertPointToScreen(omnibox_view, &point);
    EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
        point.x(), point.y(),
        base::BindOnce(&DragAndDropBrowserTestNoParam::Step2,
                       base::Unretained(this), std::move(quit))));
  }

  void Step2(base::OnceClosure quit) {
    // Simulate mouse down.
    EXPECT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
        ui_controls::LEFT, ui_controls::DOWN,
        base::BindOnce(&DragAndDropBrowserTestNoParam::Step3,
                       base::Unretained(this), std::move(quit))));
  }

  void Step3(base::OnceClosure quit) {
    // Simulate mouse move to WebContents.
    // Keep sending mouse move until the current tab is closed.
    // After the current tab is closed, send mouse up to end drag and drop.
    if (browser()->tab_strip_model()->count() == 1) {
      EXPECT_TRUE(ui_controls::SendMouseEventsNotifyWhenDone(
          ui_controls::LEFT, ui_controls::UP, std::move(quit)));
      return;
    }

    gfx::Rect bounds = browser()
                           ->tab_strip_model()
                           ->GetActiveWebContents()
                           ->GetContainerBounds();
    EXPECT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone(
        bounds.CenterPoint().x(), bounds.CenterPoint().y(),
        base::BindOnce(&DragAndDropBrowserTestNoParam::Step3,
                       base::Unretained(this), std::move(quit))));
  }
};

// https://crbug.com/1312505
IN_PROC_BROWSER_TEST_F(DragAndDropBrowserTestNoParam, CloseTabDuringDrag) {
  EXPECT_EQ(1, browser()->tab_strip_model()->count());
  ui_test_utils::TabAddedWaiter wait_for_new_tab(browser());

  // Create a new tab that closes itself on dragover event.
  ASSERT_TRUE(ExecJs(browser()
                         ->tab_strip_model()
                         ->GetActiveWebContents()
                         ->GetPrimaryMainFrame(),
                     "window.open('javascript:document.addEventListener("
                     "\"dragover\", () => {window.close(); })');"));

  wait_for_new_tab.Wait();

  EXPECT_EQ(2, browser()->tab_strip_model()->count());

  base::RunLoop loop;
  SimulateDragFromOmniboxToWebContents(loop.QuitClosure());
  loop.Run();

  EXPECT_EQ(1, browser()->tab_strip_model()->count());
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

}  // namespace chrome