blob: e5770e6e2c358ae65f7e3987d91713eb5f5585a7 [file] [log] [blame]
// 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 <algorithm>
#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/run_loop.h"
#include "base/strings/escape.h"
#include "base/strings/pattern.h"
#include "base/strings/string_number_conversions.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 "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/ui_features.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"
#if BUILDFLAG(IS_WIN)
#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
#endif
namespace chrome {
namespace {
using ::ui::mojom::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 {
public:
explicit DragAndDropSimulator(content::WebContents* web_contents)
: DragAndDropSimulator(web_contents, web_contents) {}
DragAndDropSimulator(content::WebContents* drag_contents,
content::WebContents* drop_contents)
: drag_contents_(drag_contents), drop_contents_(drop_contents) {}
DragAndDropSimulator(const DragAndDropSimulator&) = delete;
DragAndDropSimulator& operator=(const DragAndDropSimulator&) = delete;
// Simulates notification that |text| was dragged from outside of the browser,
// into the specified |location| inside |web_contents|.
// |location| is relative to |web_contents|.
// Returns true upon success.
bool SimulateDragEnter(const gfx::Point& location, const std::string& text) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
os_exchange_data_->SetString(base::UTF8ToUTF16(text));
return SimulateDragEnter(location, *os_exchange_data_);
}
// Simulates notification that |url| was dragged from outside of the browser,
// into the specified |location| inside |web_contents|.
// |location| is relative to |web_contents|.
// Returns true upon success.
bool SimulateDragEnter(const gfx::Point& location, const GURL& url) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
os_exchange_data_->SetURL(url, base::UTF8ToUTF16(url.spec()));
return SimulateDragEnter(location, *os_exchange_data_);
}
// Simulates notification that |file| was dragged from outside of the browser,
// into the specified |location| inside |web_contents|.
// |location| is relative to |web_contents|.
// Returns true upon success.
bool SimulateDragEnter(const gfx::Point& location,
const base::FilePath& file) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
os_exchange_data_->SetFilename(file);
return SimulateDragEnter(location, *os_exchange_data_);
}
// Simulates notification that multiple files were dragged from outside of the
// browser, into the specified `location` inside `web_contents`. `location` is
// relative to `web_contents`. Returns true upon success.
bool SimulateDragEnter(const gfx::Point& location,
const std::vector<ui::FileInfo>& file_infos) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
os_exchange_data_->SetFilenames(file_infos);
return SimulateDragEnter(location, *os_exchange_data_);
}
#if BUILDFLAG(IS_WIN)
// Simulates notification that multiple virtual files were dragged from
// outside of the browser, into the specified `location` inside
// `web_contents`. `location` is relative to `web_contents`. Returns true upon
// success.
bool SimulateDragEnter(
const gfx::Point& location,
const std::vector<std::pair<base::FilePath, std::string>>&
filenames_and_contents,
DWORD tymed) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
os_exchange_data_->provider().SetVirtualFileContentsForTesting(
filenames_and_contents, tymed);
return SimulateDragEnter(location, *os_exchange_data_);
}
#endif // BUILDFLAG(IS_WIN)
// Simulates notification that |url| was dragged from outside of the browser,
// into the specified |location| inside |omnibox|.
// |location| is relative to |omnibox|.
// Returns true upon success.
bool SimulateOmniboxDragEnter(aura::Window* omnibox,
const gfx::Point& location,
const GURL& url) {
os_exchange_data_ = std::make_unique<ui::OSExchangeData>();
os_exchange_data_->SetURL(url, base::UTF8ToUTF16(url.spec()));
if (active_drag_event_) {
ADD_FAILURE() << "Cannot start a new drag when old one hasn't ended yet.";
return false;
}
aura::client::DragDropDelegate* delegate =
GetOmniboxDragDropDelegate(omnibox);
if (!delegate) {
return false;
}
active_drag_event_ = base::WrapUnique(new ui::DropTargetEvent(
*os_exchange_data_, gfx::PointF(location), gfx::PointF(location),
kDefaultSourceOperations));
delegate->OnDragEntered(*active_drag_event_);
delegate->OnDragUpdated(*active_drag_event_);
return true;
}
// Simulates dropping of the drag-and-dropped item.
// SimulateDragEnter needs to be called first.
// Returns true upon success.
bool SimulateDrop(const gfx::Point& location) {
if (!active_drag_event_) {
ADD_FAILURE() << "Cannot drop a drag that hasn't started yet.";
return false;
}
aura::client::DragDropDelegate* delegate = GetDropDelegate();
if (!delegate) {
return false;
}
gfx::PointF event_location;
gfx::PointF event_root_location;
CalculateEventLocations(location, &event_location, &event_root_location,
drop_contents_);
active_drag_event_->set_location_f(event_location);
active_drag_event_->set_root_location_f(event_root_location);
delegate->OnDragUpdated(*active_drag_event_);
auto drop_cb = delegate->GetDropCallback(*active_drag_event_);
// 'drop_cb' should have a value because WebContentsViewAura
// (DragDropDelegate) doesn't return NullCallback.
DCHECK(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(os_exchange_data_), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
return true;
}
// Simulates dropping of the drag-and-dropped item into |omnibox|.
// SimulateDragEnter needs to be called first.
// Returns true upon success.
bool SimulateOmniboxDrop(aura::Window* omnibox, const gfx::Point& location) {
if (!active_drag_event_) {
ADD_FAILURE() << "Cannot drop a drag that hasn't started yet.";
return false;
}
aura::client::DragDropDelegate* delegate =
GetOmniboxDragDropDelegate(omnibox);
if (!delegate) {
return false;
}
active_drag_event_->set_location_f(gfx::PointF(location));
active_drag_event_->set_root_location_f(gfx::PointF(location));
delegate->OnDragUpdated(*active_drag_event_);
auto drop_cb = delegate->GetDropCallback(*active_drag_event_);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(os_exchange_data_), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
return true;
}
private:
bool SimulateDragEnter(const gfx::Point& location,
const ui::OSExchangeData& data) {
if (active_drag_event_) {
ADD_FAILURE() << "Cannot start a new drag when old one hasn't ended yet.";
return false;
}
aura::client::DragDropDelegate* delegate = GetDragDelegate();
if (!delegate) {
return false;
}
gfx::PointF event_location;
gfx::PointF event_root_location;
CalculateEventLocations(location, &event_location, &event_root_location,
drag_contents_);
active_drag_event_ = base::WrapUnique(new ui::DropTargetEvent(
data, event_location, event_root_location, kDefaultSourceOperations));
delegate->OnDragEntered(*active_drag_event_);
delegate->OnDragUpdated(*active_drag_event_);
return true;
}
aura::client::DragDropDelegate* GetDragDelegate() {
gfx::NativeView view = drag_contents_->GetContentNativeView();
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(view);
EXPECT_TRUE(delegate) << "Expecting WebContents to have DragDropDelegate";
return delegate;
}
aura::client::DragDropDelegate* GetDropDelegate() {
gfx::NativeView view = drop_contents_->GetContentNativeView();
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(view);
EXPECT_TRUE(delegate) << "Expecting WebContents to have DragDropDelegate";
return delegate;
}
aura::client::DragDropDelegate* GetOmniboxDragDropDelegate(
aura::Window* omnibox) {
aura::client::DragDropDelegate* delegate =
aura::client::GetDragDropDelegate(omnibox);
EXPECT_TRUE(delegate) << "Expecting Omnibox to have DragDropDelegate";
return delegate;
}
void CalculateEventLocations(const gfx::Point& web_contents_relative_location,
gfx::PointF* out_event_location,
gfx::PointF* out_event_root_location,
content::WebContents* contents) {
gfx::NativeView view = contents->GetNativeView();
*out_event_location = gfx::PointF(web_contents_relative_location);
gfx::Point root_location = web_contents_relative_location;
aura::Window::ConvertPointToTarget(view, view->GetRootWindow(),
&root_location);
*out_event_root_location = gfx::PointF(root_location);
}
// These are ui::DropTargetEvent::source_operations_ being sent when manually
// trying out drag&drop of an image file from Nemo (Ubuntu's file explorer)
// into a content_shell.
static constexpr int kDefaultSourceOperations = ui::DragDropTypes::DRAG_MOVE |
ui::DragDropTypes::DRAG_COPY |
ui::DragDropTypes::DRAG_LINK;
// WebContents for where the drag and drop occurs. These can be the same if
// the drag and drop happens within the same WebContents.
raw_ptr<content::WebContents> drag_contents_;
raw_ptr<content::WebContents> drop_contents_;
std::unique_ptr<ui::DropTargetEvent> active_drag_event_;
std::unique_ptr<ui::OSExchangeData> os_exchange_data_;
};
// 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 {
public:
// Starts monitoring |web_contents| for a start of a drag-and-drop.
explicit DragStartWaiter(content::WebContents* web_contents)
: web_contents_(web_contents),
message_loop_runner_(new content::MessageLoopRunner) {
DCHECK(web_contents_);
// Intercept calls to the old DragDropClient.
gfx::NativeWindow root_window =
web_contents_->GetContentNativeView()->GetRootWindow();
old_client_ = aura::client::GetDragDropClient(root_window);
aura::client::SetDragDropClient(root_window, this);
}
~DragStartWaiter() override {
// Restore the original DragDropClient.
gfx::NativeWindow root_window =
web_contents_->GetContentNativeView()->GetRootWindow();
aura::client::SetDragDropClient(root_window, old_client_);
}
DragStartWaiter(const DragStartWaiter&) = delete;
DragStartWaiter& operator=(const DragStartWaiter&) = delete;
// Waits until we almost report a drag-and-drop start to the OS.
// At that point
// 1) the callback from PostTaskWhenDragStarts will be posted.
// 2) the drag-start request will be forwarded to the OS
// (unless SuppressPassingStartDragFurther method was called).
//
// Note that if SuppressPassingStartDragFurther was not called then
// WaitUntilDragStart can take a long time to return (it returns only after
// the OS decides that the drag-and-drop has ended).
//
// Before returning populates `source_origin`, `text`, `html` and other
// parameters with data that would have been passed to the OS). If the caller
// is not interested in this data, then the corresponding argument can be
// null.
void WaitUntilDragStart(std::optional<url::Origin>* source_origin,
std::string* text,
std::string* html,
int* operation,
gfx::Point* location_inside_web_contents) {
message_loop_runner_->Run();
// message_loop_runner_->Quit is only called from StartDragAndDrop.
DCHECK(drag_started_);
if (source_origin) {
*source_origin = source_origin_;
}
if (text) {
*text = text_;
}
if (html) {
*html = html_;
}
if (operation) {
*operation = operation_;
}
if (location_inside_web_contents) {
*location_inside_web_contents = location_inside_web_contents_;
}
}
void WaitUntilDragStart() {
WaitUntilDragStart(nullptr, nullptr, nullptr, nullptr, nullptr);
}
void SuppressPassingStartDragFurther() {
suppress_passing_of_start_drag_further_ = true;
}
void PostTaskWhenDragStarts(base::OnceClosure callback) {
callback_to_run_inside_drag_and_drop_message_loop_ = std::move(callback);
}
// aura::client::DragDropClient overrides:
DragOperation StartDragAndDrop(std::unique_ptr<ui::OSExchangeData> data,
aura::Window* root_window,
aura::Window* source_window,
const gfx::Point& screen_location,
int allowed_operations,
ui::mojom::DragEventSource source) override {
DCHECK(!drag_started_);
if (!drag_started_) {
drag_started_ = true;
message_loop_runner_->Quit();
source_origin_ = data->GetRendererTaintedOrigin();
std::optional<std::u16string> text = data->GetString();
if (text) {
text_ = base::UTF16ToUTF8(*text);
} else {
text_ = "<no text>";
}
std::optional<ui::OSExchangeData::HtmlInfo> html_content =
data->GetHtml();
if (html_content.has_value()) {
html_ = base::UTF16ToUTF8(html_content->html);
} else {
html_ = "<no html>";
}
gfx::Rect bounds =
web_contents_->GetContentNativeView()->GetBoundsInScreen();
location_inside_web_contents_ =
screen_location - gfx::Vector2d(bounds.x(), bounds.y());
operation_ = allowed_operations;
}
if (!callback_to_run_inside_drag_and_drop_message_loop_.is_null()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
std::move(callback_to_run_inside_drag_and_drop_message_loop_));
callback_to_run_inside_drag_and_drop_message_loop_.Reset();
}
if (suppress_passing_of_start_drag_further_) {
return DragOperation::kNone;
}
// Start a nested drag-and-drop loop (might not return for a long time).
return old_client_->StartDragAndDrop(std::move(data), root_window,
source_window, screen_location,
allowed_operations, source);
}
void DragCancel() override {
ADD_FAILURE() << "Unexpected call to DragCancel";
}
#if BUILDFLAG(IS_LINUX)
void UpdateDragImage(const gfx::ImageSkia& image,
const gfx::Vector2d& offset) override {}
#endif
bool IsDragDropInProgress() override { return drag_started_; }
void AddObserver(aura::client::DragDropClientObserver* observer) override {}
void RemoveObserver(aura::client::DragDropClientObserver* observer) override {
}
private:
raw_ptr<content::WebContents> web_contents_;
scoped_refptr<content::MessageLoopRunner> message_loop_runner_;
raw_ptr<aura::client::DragDropClient> old_client_;
base::OnceClosure callback_to_run_inside_drag_and_drop_message_loop_;
bool suppress_passing_of_start_drag_further_ = false;
// Data captured during the first intercepted StartDragAndDrop call.
bool drag_started_ = false;
std::optional<url::Origin> source_origin_;
std::string text_;
std::string html_;
int operation_;
gfx::Point location_inside_web_contents_;
};
// Helper for waiting for notifications from
// content/test/data/drag_and_drop/event_monitoring.js
class DOMDragEventWaiter {
public:
DOMDragEventWaiter(const std::string& event_type_to_wait_for,
const content::ToRenderFrameHost& target)
: target_frame_name_(target.render_frame_host()->GetFrameName()),
event_type_to_wait_for_(event_type_to_wait_for),
dom_message_queue_(content::WebContents::FromRenderFrameHost(
target.render_frame_host())) {}
DOMDragEventWaiter(const DOMDragEventWaiter&) = delete;
DOMDragEventWaiter& operator=(const DOMDragEventWaiter&) = delete;
// Waits until |target| calls reportDragEvent in
// chrome/test/data/drag_and_drop/event_monitoring.js with event_type
// property set to |event_type_to_wait_for|. (|target| and
// |event_type_to_wait_for| are passed to the constructor).
//
// Returns the event details via |found_event| (in form of a JSON-encoded
// object). See chrome/test/data/drag_and_drop/event_monitoring.js for keys
// and properties that |found_event| is expected to have.
//
// Returns true upon success. It is okay if |response| is null.
[[nodiscard]] bool WaitForNextMatchingEvent(std::string* found_event) {
std::string candidate_event;
bool got_right_event_type = false;
bool got_right_window_name = false;
do {
if (!dom_message_queue_.WaitForMessage(&candidate_event)) {
return false;
}
got_right_event_type =
IsExpectedEventType(candidate_event, event_type_to_wait_for_);
got_right_window_name =
IsExpectedWindowName(candidate_event, target_frame_name_);
} while (!got_right_event_type || !got_right_window_name);
if (found_event) {
*found_event = candidate_event;
}
return true;
}
static bool IsExpectedEventType(const std::string& actual_event_body,
const std::string& expected_event_type) {
return IsExpectedPropertyValue(actual_event_body, "event_type",
expected_event_type);
}
static bool IsExpectedWindowName(const std::string& actual_event_body,
const std::string& expected_window_name) {
return IsExpectedPropertyValue(actual_event_body, "window_name",
expected_window_name);
}
private:
static bool IsExpectedPropertyValue(
const std::string& actual_event_body,
const std::string& property_name,
const std::string& expected_property_value) {
return base::MatchPattern(
actual_event_body,
base::StringPrintf("*\"%s\":\"%s\"*", property_name.c_str(),
expected_property_value.c_str()));
}
std::string target_frame_name_;
std::string event_type_to_wait_for_;
content::DOMMessageQueue dom_message_queue_;
};
// Helper for verifying contents of DOM events associated with drag-and-drop.
class DOMDragEventVerifier {
public:
DOMDragEventVerifier() = default;
DOMDragEventVerifier(const DOMDragEventVerifier&) = delete;
DOMDragEventVerifier& operator=(const DOMDragEventVerifier&) = delete;
void set_expected_client_position(const std::string& value) {
expected_client_position_ = value;
}
void set_expected_drop_effect(const std::string& value) {
expected_drop_effect_ = value;
}
void set_expected_effect_allowed(const std::string& value) {
expected_effect_allowed_ = value;
}
void set_expected_file_names(const std::string& value) {
expected_file_names_ = value;
}
void set_expected_mime_types(const std::string& value) {
expected_mime_types_ = value;
}
void set_expected_page_position(const std::string& value) {
expected_page_position_ = value;
}
void set_expected_screen_position(const std::string& value) {
expected_screen_position_ = value;
}
// Returns a matcher that will match a std::string (drag event data - e.g.
// one returned by DOMDragEventWaiter::WaitForNextMatchingEvent) if it matches
// the expectations of this DOMDragEventVerifier.
testing::Matcher<std::string> Matches() const {
return testing::AllOf(
FieldMatches("client_position", expected_client_position_),
FieldMatches("drop_effect", expected_drop_effect_),
FieldMatches("effect_allowed", expected_effect_allowed_),
FieldMatches("file_names", expected_file_names_),
FieldMatches("mime_types", expected_mime_types_),
FieldMatches("page_position", expected_page_position_),
FieldMatches("screen_position", expected_screen_position_));
}
private:
static testing::Matcher<std::string> FieldMatches(
const std::string& field_name,
const std::string& expected_value) {
if (expected_value == "<no expectation>") {
return testing::A<std::string>();
}
return testing::HasSubstr(base::StringPrintf(
"\"%s\":\"%s\"", field_name.c_str(), expected_value.c_str()));
}
std::string expected_drop_effect_ = "<no expectation>";
std::string expected_effect_allowed_ = "<no expectation>";
std::string expected_file_names_ = "<no expectation>";
std::string expected_mime_types_ = "<no expectation>";
std::string expected_client_position_ = "<no expectation>";
std::string expected_page_position_ = "<no expectation>";
std::string expected_screen_position_ = "<no expectation>";
};
// 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 {
public:
explicit DOMDragEventCounter(const content::ToRenderFrameHost& target)
: target_frame_name_(target.render_frame_host()->GetFrameName()),
dom_message_queue_(content::WebContents::FromRenderFrameHost(
target.render_frame_host())) {}
DOMDragEventCounter(const DOMDragEventCounter&) = delete;
DOMDragEventCounter& operator=(const DOMDragEventCounter&) = delete;
// Resets all the accumulated event counts to zeros.
void Reset() {
StoreAccumulatedEvents();
received_events_.clear();
}
// Returns the number of events of the specified |event_type| received since
// construction, or since the last time Reset was called. |event_type| should
// be one of possible |type| property values for a DOM drag-and-drop event -
// e.g. "dragenter" or "dragover".
int GetNumberOfReceivedEvents(const std::string& event_type) {
std::vector<std::string> v({event_type});
return GetNumberOfReceivedEvents(v.begin(), v.end());
}
// Returns the number of events of the specified |event_types| received since
// construction, or since the last time Reset was called. Elements of
// |event_types| should be one of possible |type| property values for a DOM
// drag-and-drop event - e.g. "dragenter" or "dragover".
int GetNumberOfReceivedEvents(
std::initializer_list<const char*> event_types) {
return GetNumberOfReceivedEvents(event_types.begin(), event_types.end());
}
private:
template <typename T>
int GetNumberOfReceivedEvents(T event_types_begin, T event_types_end) {
StoreAccumulatedEvents();
auto received_event_has_matching_event_type =
[&event_types_begin,
&event_types_end](const std::string& received_event) {
return std::any_of(event_types_begin, event_types_end,
[&received_event](const std::string& event_type) {
return DOMDragEventWaiter::IsExpectedEventType(
received_event, event_type);
});
};
return std::ranges::count_if(received_events_,
received_event_has_matching_event_type);
}
void StoreAccumulatedEvents() {
std::string candidate_event;
while (dom_message_queue_.PopMessage(&candidate_event)) {
if (DOMDragEventWaiter::IsExpectedWindowName(candidate_event,
target_frame_name_)) {
received_events_.push_back(candidate_event);
}
}
}
std::string target_frame_name_;
content::DOMMessageQueue dom_message_queue_;
std::vector<std::string> received_events_;
};
const char kTestPagePath[] = "/drag_and_drop/page.html";
// bool use_cross_site_subframe, double device_scale_factor.
using TestParam = std::tuple<bool, double>;
} // 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> {
public:
DragAndDropBrowserTest()
: https_test_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
DragAndDropBrowserTest(const DragAndDropBrowserTest&) = delete;
DragAndDropBrowserTest& operator=(const DragAndDropBrowserTest&) = delete;
struct DragImageBetweenFrames_TestState;
void DragImageBetweenFrames_Start(bool image_same_origin,
bool image_crossorigin_attr);
void DragImageBetweenFrames_Step2(DragImageBetweenFrames_TestState*);
void DragImageBetweenFrames_Step3(DragImageBetweenFrames_TestState*);
struct DragImageFromDisappearingFrame_TestState;
void DragImageFromDisappearingFrame_Step2(
DragImageFromDisappearingFrame_TestState*);
void DragImageFromDisappearingFrame_Step3(
DragImageFromDisappearingFrame_TestState*);
struct CrossSiteDrag_TestState;
void CrossSiteDrag_Step2(CrossSiteDrag_TestState*);
void CrossSiteDrag_Step3(CrossSiteDrag_TestState*);
struct CrossNavCrossSiteDrag_TestState;
void CrossNavCrossSiteDrag_Step2(CrossNavCrossSiteDrag_TestState*);
void CrossNavCrossSiteDrag_Step3(CrossNavCrossSiteDrag_TestState*);
struct CrossTabDrag_TestState;
void CrossTabDrag_Step2(CrossTabDrag_TestState*);
void CrossTabDrag_Step3(CrossTabDrag_TestState*);
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(
"force-device-scale-factor",
base::NumberToString(std::get<1>(GetParam())));
}
void SetUp() override {
// TODO(crbug.com/394369035): The parameters for the width of the drop
// targets have not been determined yet, and may interfere with the tests
// below by shifting the contents around.
// These overrides should be removed once the parameters are finalized.
feature_list_.InitWithFeaturesAndParameters(
{{features::kSideBySide,
{{features::kSideBySideDropTargetMinWidth.name, "0"},
{features::kSideBySideDropTargetMaxWidth.name, "0"}}},
{features::kSideBySideDropTargetNudge,
{{features::kSideBySideDropTargetNudgeMinWidth.name, "0"},
{features::kSideBySideDropTargetNudgeMaxWidth.name, "0"},
{features::kSideBySideDropTargetNudgeToFullMinWidth.name, "0"},
{features::kSideBySideDropTargetNudgeToFullMaxWidth.name, "0"}}}},
{});
InProcessBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_test_server()->AddDefaultHandlers(GetChromeTestDataDir());
https_test_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
content::SetupCrossSiteRedirector(https_test_server());
ASSERT_TRUE(https_test_server()->Start());
drag_simulator_ = std::make_unique<DragAndDropSimulator>(web_contents());
}
void TearDownOnMainThread() override {
// For X11 need to tear down before UI goes away.
drag_simulator_.reset();
}
bool use_cross_site_subframe() {
// This is controlled by gtest's test param from INSTANTIATE_TEST_SUITE_P.
return std::get<0>(GetParam());
}
content::RenderFrameHost* GetLeftFrame(
content::WebContents* contents = nullptr) {
AssertTestPageIsLoaded();
return GetFrameByName("left", contents);
}
content::RenderFrameHost* GetRightFrame(
content::WebContents* contents = nullptr) {
AssertTestPageIsLoaded();
return GetFrameByName("right", contents);
}
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
//////////////////////
// Navigation helpers.
bool NavigateToTestPage(const std::string& origin) {
return NavigateMainFrame(origin, kTestPagePath);
}
bool NavigateMainFrame(const std::string& origin, const std::string& path) {
GURL url = https_test_server()->GetURL(origin, path);
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
return web_contents()->GetLastCommittedURL() == url;
}
// Navigates the left frame to |filename| (found under
// chrome/test/data/drag_and_drop directory).
bool NavigateLeftFrame(const std::string& frame_origin,
const std::string& image_origin,
bool image_crossorigin_attr,
const std::string& filename) {
AssertTestPageIsLoaded();
base::StringPairs replacement_text;
replacement_text.emplace_back(
"REPLACE_WITH_HOST_AND_PORT",
base::StringPrintf("%s:%d", image_origin.c_str(),
https_test_server()->port()));
replacement_text.emplace_back(
"REPLACE_WITH_CROSSORIGIN",
std::string(image_crossorigin_attr ? "crossorigin" : ""));
std::string path = net::test_server::GetFilePathWithReplacements(
filename, replacement_text);
return NavigateNamedFrame("left", frame_origin, path);
}
// Navigates the left frame to |filename| (found under
// chrome/test/data/drag_and_drop directory).
bool NavigateLeftFrame(const std::string& origin,
const std::string& filename) {
return NavigateLeftFrame(origin, origin, false, filename);
}
// Navigates the right frame to |filename| (found under
// chrome/test/data/drag_and_drop directory).
bool NavigateRightFrame(const std::string& origin,
const std::string& filename) {
AssertTestPageIsLoaded();
return NavigateNamedFrame("right", origin, filename);
}
bool NavigateNamedFrame(const std::string& frame_name,
const std::string& origin,
const std::string& filename,
content::WebContents* web_contents = nullptr) {
content::RenderFrameHost* frame = GetFrameByName(frame_name, web_contents);
if (!frame) {
return false;
}
// Navigate the frame and wait for the load event.
std::string script = base::StringPrintf(
"location.href = '/cross-site/%s/drag_and_drop/%s';\n"
"new Promise(resolve => {"
" setTimeout(function() { resolve(42); }, 0);"
"});",
origin.c_str(), filename.c_str());
content::TestFrameNavigationObserver observer(frame);
if (content::EvalJs(frame, script).ExtractInt() != 42) {
return false;
}
observer.Wait();
// |frame| might have been swapped-out during a cross-site navigation,
// therefore we need to get the current RenderFrameHost to work against
// going forward.
frame = web_contents ? GetFrameByName(frame_name, web_contents)
: GetFrameByName(frame_name);
DCHECK(frame);
// Wait until hit testing data is ready.
WaitForHitTestData(frame);
return true;
}
////////////////////////////////////////////////////////////
// Simulation of starting a drag-and-drop (using the mouse).
bool SimulateMouseDownAndDragStartInLeftFrame() {
AssertTestPageIsLoaded();
// Waiting until the mousemove and mousedown events reach the right renderer
// is needed to avoid flakiness reported in https://crbug.com/671445 (which
// has its root cause in https://crbug.com/647378). Once the latter bug
// is fixed, we should no longer need to wait for these events (because
// fixing https://crbug.com/647378 should guarantee that events arrive
// to the renderer in the right order).
DOMDragEventWaiter mouse_move_event_waiter("mousemove", GetLeftFrame());
DOMDragEventWaiter mouse_down_event_waiter("mousedown", GetLeftFrame());
if (!SimulateMouseMove(kMiddleOfLeftFrame)) {
return false;
}
if (!mouse_move_event_waiter.WaitForNextMatchingEvent(nullptr)) {
return false;
}
if (!SimulateMouseDown()) {
return false;
}
if (!mouse_down_event_waiter.WaitForNextMatchingEvent(nullptr)) {
return false;
}
if (!SimulateMouseMove(expected_location_of_drag_start_in_left_frame())) {
return false;
}
return true;
}
gfx::Point expected_location_of_drag_start_in_left_frame() {
// TODO(crbug.com/41279378): The delta below should exceed kDragThresholdX
// and kDragThresholdY from MouseEventManager.cpp in blink. Ideally, it
// would come from the OS instead.
return kMiddleOfLeftFrame + gfx::Vector2d(10, 10);
}
bool SimulateMouseMoveToLeftFrame() {
AssertTestPageIsLoaded();
return SimulateMouseMove(kMiddleOfLeftFrame);
}
bool SimulateMouseMoveToRightFrame() {
AssertTestPageIsLoaded();
return SimulateMouseMove(kMiddleOfRightFrame);
}
bool SimulateMouseUp() {
return ui_test_utils::SendMouseEventsSync(ui_controls::LEFT,
ui_controls::UP);
}
////////////////////////////////////////////////////////////////////
// Simulation of dragging from outside the browser into web contents
// (using DragAndDropSimulator, not simulating mouse events).
bool SimulateDragEnterToRightFrame(const std::string& text) {
AssertTestPageIsLoaded();
return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, text);
}
bool SimulateDragEnterToRightFrame(const GURL& url) {
AssertTestPageIsLoaded();
return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, url);
}
bool SimulateDragEnterToRightFrame(const base::FilePath& file) {
AssertTestPageIsLoaded();
return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, file);
}
bool SimulateDragEnterToRightFrame(
const std::vector<ui::FileInfo>& file_infos) {
AssertTestPageIsLoaded();
return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, file_infos);
}
#if BUILDFLAG(IS_WIN)
bool SimulateDragEnterToRightFrame(
const std::vector<std::pair<base::FilePath, std::string>>& file_infos,
DWORD tymed) {
AssertTestPageIsLoaded();
return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, file_infos,
tymed);
}
#endif // BUILDFLAG(IS_WIN)
bool SimulateDropInRightFrame() {
AssertTestPageIsLoaded();
return drag_simulator_->SimulateDrop(kMiddleOfRightFrame);
}
bool SimulateDragEnterToOmnibox(const GURL& url) {
AssertTestPageIsLoaded();
BrowserView* browser_view =
BrowserView::GetBrowserViewForBrowser(browser());
OmniboxViewViews* omnibox_view =
browser_view->toolbar()->location_bar()->omnibox_view();
gfx::Point point;
views::View::ConvertPointToScreen(omnibox_view, &point);
return drag_simulator_->SimulateOmniboxDragEnter(
omnibox_view->GetWidget()->GetNativeView(), point, url);
}
bool SimulateDropInOmnibox() {
AssertTestPageIsLoaded();
BrowserView* browser_view =
BrowserView::GetBrowserViewForBrowser(browser());
OmniboxViewViews* omnibox_view =
browser_view->toolbar()->location_bar()->omnibox_view();
gfx::Point point;
views::View::ConvertPointToScreen(omnibox_view, &point);
return drag_simulator_->SimulateOmniboxDrop(
omnibox_view->GetWidget()->GetNativeView(), point);
}
gfx::Point GetMiddleOfRightFrameInScreenCoords() {
aura::Window* window = web_contents()->GetNativeView();
aura::client::ScreenPositionClient* screen_position_client =
aura::client::GetScreenPositionClient(window->GetRootWindow());
gfx::Point screen_position(kMiddleOfRightFrame);
if (screen_position_client) {
screen_position_client->ConvertPointToScreen(window, &screen_position);
}
return screen_position;
}
net::EmbeddedTestServer* https_test_server() { return &https_test_server_; }
private:
// Constants with coordinates within content/test/data/drag_and_drop/page.html
// The precise frame center is at 200,200 and 400,200 coordinates, but slight
// differences between left and right frame hopefully make it easier to detect
// incorrect dom_drag_and_drop_event.clientX/Y values in test asserts.
const gfx::Point kMiddleOfLeftFrame = gfx::Point(155, 150);
const gfx::Point kMiddleOfRightFrame = gfx::Point(455, 250);
bool SimulateMouseDown() {
return ui_test_utils::SendMouseEventsSync(ui_controls::LEFT,
ui_controls::DOWN);
}
bool SimulateMouseMove(const gfx::Point& location_inside_web_contents) {
gfx::Rect bounds = web_contents()->GetContainerBounds();
return ui_test_utils::SendMouseMoveSync(
gfx::Point(bounds.x() + location_inside_web_contents.x(),
bounds.y() + location_inside_web_contents.y()));
}
content::RenderFrameHost* GetFrameByName(
const std::string& name_to_find,
content::WebContents* contents = nullptr) {
return contents ? content::FrameMatchingPredicate(
contents->GetPrimaryPage(),
base::BindRepeating(&content::FrameMatchesName,
name_to_find))
: content::FrameMatchingPredicate(
web_contents()->GetPrimaryPage(),
base::BindRepeating(&content::FrameMatchesName,
name_to_find));
}
void AssertTestPageIsLoaded() {
ASSERT_EQ(kTestPagePath, web_contents()->GetLastCommittedURL().path());
}
std::unique_ptr<DragAndDropSimulator> drag_simulator_;
net::EmbeddedTestServer https_test_server_;
base::test::ScopedFeatureList feature_list_;
};
// 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) {
// TODO (crbug/1521094): Test fails since 2023 refresh.
if (std::get<double>(GetParam()) > 1.5) {
GTEST_SKIP();
}
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "drop_target.html"));
// Setup test expectations.
DOMDragEventVerifier expected_dom_event_data;
expected_dom_event_data.set_expected_client_position("(155, 150)");
expected_dom_event_data.set_expected_drop_effect("copy");
expected_dom_event_data.set_expected_effect_allowed("all");
expected_dom_event_data.set_expected_mime_types("text/plain");
expected_dom_event_data.set_expected_page_position("(155, 150)");
// Drag text from outside the browser into/over the right frame.
{
DOMDragEventWaiter dragover_waiter("dragover", GetRightFrame());
ASSERT_TRUE(SimulateDragEnterToRightFrame("Dragged test text"));
std::string dragover_event;
ASSERT_TRUE(dragover_waiter.WaitForNextMatchingEvent(&dragover_event));
EXPECT_THAT(dragover_event, expected_dom_event_data.Matches());
}
// Allow the dragenter and dragover events to update the current drag
// operations on the WebContentsView before proceeding to the drop, since
// the latter needs that information to determine whether to force a
// default action in the renderer process.
base::RunLoop().RunUntilIdle();
// Drop into the right frame.
{
// Setup drop event expectations (dropEffect changes to "copy" during drop).
DOMDragEventVerifier expected_drop_event_data;
expected_drop_event_data.set_expected_client_position("(155, 150)");
expected_drop_event_data.set_expected_drop_effect("copy");
expected_drop_event_data.set_expected_effect_allowed("all");
expected_drop_event_data.set_expected_mime_types("text/plain");
expected_drop_event_data.set_expected_page_position("(155, 150)");
DOMDragEventWaiter drop_waiter("drop", GetRightFrame());
ASSERT_TRUE(SimulateDropInRightFrame());
std::string drop_event;
ASSERT_TRUE(drop_waiter.WaitForNextMatchingEvent(&drop_event));
EXPECT_THAT(drop_event, expected_drop_event_data.Matches());
}
}
// 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) {
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "title1.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::NavigationController& controller = web_contents->GetController();
int initial_history_count = controller.GetEntryCount();
GURL initial_url = web_contents->GetPrimaryMainFrame()->GetLastCommittedURL();
ASSERT_EQ(1, browser()->tab_strip_model()->count());
// Focus the omnibox.
chrome::FocusLocationBar(browser());
EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER));
// Drag a normal URL from outside the browser into/over the right frame.
GURL dragged_url = https_test_server()->GetURL("d.test", "/title2.html");
ASSERT_TRUE(SimulateDragEnterToRightFrame(dragged_url));
ui_test_utils::TabAddedWaiter wait_for_new_tab(browser());
// Drop into the right frame.
ASSERT_TRUE(SimulateDropInRightFrame());
// Verify that dropping |dragged_url| creates a new tab and navigates it to
// that URL.
wait_for_new_tab.Wait();
EXPECT_EQ(2, browser()->tab_strip_model()->count());
content::WebContents* new_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver(new_web_contents, 1).Wait();
EXPECT_EQ(dragged_url,
new_web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
// Verify that the initial tab didn't navigate.
EXPECT_EQ(initial_url,
web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
EXPECT_EQ(initial_history_count, controller.GetEntryCount());
// Verify that the focus moved from the omnibox to the tab contents.
EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER));
}
#if BUILDFLAG(IS_WIN)
// Scenario: Drag and drop a file from outside the browser and it should have
// associated file type, fetched from it's diplay_name. Test coverage:
// dragenter, dragover, drop DOM events. Note: this test uses a file with a
// known extension and temporary path.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DragAndDropVirtualFiles) {
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame("a.test", "drop_target.html"));
// Prepare a test file with a known extension and temporary path.
std::vector<std::pair<base::FilePath, std::string>> file_infos;
base::FilePath test_file = ui_test_utils::GetTestFilePath(
base::FilePath(), base::FilePath().AppendASCII("test_document.pdf"));
file_infos.emplace_back(test_file, std::string("just some data"));
// Set up a script in the right frame to listen for dragenter, dragover, and
// drop, and record file type for each event.
ASSERT_TRUE(ExecJs(GetRightFrame(),
R"(
window.eventFileTypes = {dragenter: '', dragover: '', drop: ''};
document.addEventListener('dragenter', function(e) {
if (e.dataTransfer && e.dataTransfer.items &&
e.dataTransfer.items.length > 0) {
window.eventFileTypes.dragenter = e.dataTransfer.items[0].type;
}
});
document.addEventListener('dragover', function(e) {
if (e.dataTransfer && e.dataTransfer.items &&
e.dataTransfer.items.length > 0) {
window.eventFileTypes.dragover = e.dataTransfer.items[0].type;
}
});
document.addEventListener('drop', function(e) {
if (e.dataTransfer && e.dataTransfer.items &&
e.dataTransfer.items.length > 0) {
window.eventFileTypes.drop = e.dataTransfer.items[0].type;
}
});
)"));
// Simulate dragging the file into the right frame.
DOMDragEventWaiter dragenter_waiter("dragenter", GetRightFrame());
ASSERT_TRUE(SimulateDragEnterToRightFrame(file_infos, TYMED_HGLOBAL));
std::string dragenter_event;
ASSERT_TRUE(dragenter_waiter.WaitForNextMatchingEvent(&dragenter_event));
// Simulate dragover event.
DOMDragEventWaiter dragover_waiter("dragover", GetRightFrame());
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
std::string dragover_event;
ASSERT_TRUE(dragover_waiter.WaitForNextMatchingEvent(&dragover_event));
// Simulate drop event.
DOMDragEventWaiter drop_waiter("drop", GetRightFrame());
ASSERT_TRUE(SimulateDropInRightFrame());
std::string drop_event;
ASSERT_TRUE(drop_waiter.WaitForNextMatchingEvent(&drop_event));
// Query the file types received by the renderer for each event.
std::string dragenter_type =
EvalJs(GetRightFrame(), "window.eventFileTypes.dragenter")
.ExtractString();
std::string dragover_type =
EvalJs(GetRightFrame(), "window.eventFileTypes.dragover").ExtractString();
std::string drop_type =
EvalJs(GetRightFrame(), "window.eventFileTypes.drop").ExtractString();
// For a pdf file, the type should be "application/pdf".
EXPECT_TRUE(dragenter_type == "application/pdf")
<< "Renderer received dragenter file type: " << dragenter_type;
EXPECT_TRUE(dragover_type == "application/pdf")
<< "Renderer received dragover file type: " << dragover_type;
EXPECT_TRUE(drop_type == "application/pdf")
<< "Renderer received drop file type: " << drop_type;
}
#endif // BUILDFLAG(IS_WIN)
// 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 and
// consistently failing on linux-chromeos-dbg.
#if BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DropUrlIntoOmnibox DISABLED_DropUrlIntoOmnibox
#else
#define MAYBE_DropUrlIntoOmnibox DropUrlIntoOmnibox
#endif
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, MAYBE_DropUrlIntoOmnibox) {
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "title1.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
GURL initial_url = web_contents->GetPrimaryMainFrame()->GetLastCommittedURL();
ASSERT_EQ(1, browser()->tab_strip_model()->count());
// Focus the omnibox.
chrome::FocusLocationBar(browser());
EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER));
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
OmniboxViewViews* omnibox_view =
browser_view->toolbar()->location_bar()->omnibox_view();
EXPECT_TRUE(omnibox_view->IsSelectAll());
// Click into Omnibox, so the text will be unselected.
base::RunLoop loop1;
ui_test_utils::MoveMouseToCenterAndClick(omnibox_view, ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP,
loop1.QuitClosure());
loop1.Run();
EXPECT_FALSE(omnibox_view->IsSelectAll());
// Drag a normal URL from outside the browser into the Omnibox.
GURL dragged_url = https_test_server()->GetURL("d.test", "/title2.html");
ASSERT_TRUE(SimulateDragEnterToOmnibox(dragged_url));
// Drop into the Omnibox.
ASSERT_TRUE(SimulateDropInOmnibox());
// Verify that no new tab is opened.
EXPECT_EQ(1, browser()->tab_strip_model()->count());
// Verify that the dropped URL is selected.
EXPECT_TRUE(omnibox_view->IsSelectAll());
// Click into the browser center to unselect the Omnibox text.
base::RunLoop loop2;
// The omnibox popup is open, and the browser's center point falls inside,
// near the bottom of the popup. Offset the click down 5px, so that it
// actually clicks the browser window and not the popup.
ui_test_utils::MoveMouseToCenterWithOffsetAndClick(
browser_view, {0, 5}, ui_controls::LEFT,
ui_controls::DOWN | ui_controls::UP, loop2.QuitClosure());
loop2.Run();
// Verify that the dropped URL is no longer selected after the mouse clicks
// somewhere else.
EXPECT_FALSE(omnibox_view->IsSelectAll());
EXPECT_FALSE(omnibox_view->drop_cursor_visible());
}
// 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) {
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "title1.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::NavigationController& controller = web_contents->GetController();
int initial_history_count = controller.GetEntryCount();
GURL initial_url = web_contents->GetPrimaryMainFrame()->GetLastCommittedURL();
ASSERT_EQ(1, browser()->tab_strip_model()->count());
// Focus the omnibox.
chrome::FocusLocationBar(browser());
EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER));
// Drag a file from outside the browser into/over the right frame.
base::FilePath dragged_file = ui_test_utils::GetTestFilePath(
base::FilePath(), base::FilePath().AppendASCII("title3.html"));
ASSERT_TRUE(SimulateDragEnterToRightFrame(dragged_file));
ui_test_utils::TabAddedWaiter wait_for_new_tab(browser());
// Drop into the right frame.
ASSERT_TRUE(SimulateDropInRightFrame());
// Verify that dropping |dragged_file| creates a new tab and navigates it to
// the corresponding file: URL.
wait_for_new_tab.Wait();
EXPECT_EQ(2, browser()->tab_strip_model()->count());
content::WebContents* new_web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::TestNavigationObserver(new_web_contents, 1).Wait();
EXPECT_EQ(net::FilePathToFileURL(dragged_file),
new_web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
// Verify that the initial tab didn't navigate.
EXPECT_EQ(initial_url,
web_contents->GetPrimaryMainFrame()->GetLastCommittedURL());
EXPECT_EQ(initial_history_count, controller.GetEntryCount());
// Verify that the focus moved from the omnibox to the tab contents.
EXPECT_FALSE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_OMNIBOX));
EXPECT_TRUE(ui_test_utils::IsViewFocused(browser(), VIEW_ID_TAB_CONTAINER));
}
// This test verifies that dropping multiple files from outside the browser
// opens all files in new tab.
IN_PROC_BROWSER_TEST_P(DragAndDropBrowserTest, DropMultipleFilesFromOutside) {
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_EQ(1, browser()->tab_strip_model()->count());
// Drag files from outside the browser into/over the right frame.
std::vector<ui::FileInfo> file_infos;
base::FilePath dragged_file_1 = ui_test_utils::GetTestFilePath(
base::FilePath(), base::FilePath().AppendASCII("title1.html"));
base::FilePath dragged_file_2 = ui_test_utils::GetTestFilePath(
base::FilePath(), base::FilePath().AppendASCII("title2.html"));
file_infos.emplace_back(dragged_file_1, dragged_file_1.BaseName());
file_infos.emplace_back(dragged_file_2, dragged_file_2.BaseName());
ASSERT_TRUE(SimulateDragEnterToRightFrame(file_infos));
const int expected_new_tab_count = 2;
// Create a TestNavigationObserver for each expected tab.
std::vector<std::unique_ptr<content::TestNavigationObserver>> observers;
for (int i = 0; i < expected_new_tab_count; ++i) {
observers.push_back(
std::make_unique<content::TestNavigationObserver>(nullptr));
observers.back()->StartWatchingNewWebContents();
}
// Drop into the right frame.
ASSERT_TRUE(SimulateDropInRightFrame());
for (auto& observer : observers) {
observer->Wait();
}
ASSERT_EQ(3, browser()->tab_strip_model()->count());
// First file tab should be focused after drop.
EXPECT_EQ(
net::FilePathToFileURL(dragged_file_1),
browser()->tab_strip_model()->GetActiveWebContents()->GetVisibleURL());
}
// 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) {
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "title1.html"));
content::NavigationController& controller = web_contents()->GetController();
int initial_history_count = controller.GetEntryCount();
// Drag URL from outside the browser into/over the right frame. The test uses
// a URL that:
// 1. Passes RenderWidgetHostImpl::FilterDropData checks.
// 2. Fails CanDisplay checks in Blink (e.g. in RemoteFrame::Navigate).
// - This condition trigger the crash from https://crbug.com/1003169
// 3. Passes BeginNavigation checks
// - This rules out "chrome-error://blah".
GURL dragged_url("blob:null/some-guid");
ASSERT_TRUE(SimulateDragEnterToRightFrame(dragged_url));
// Drop into the right frame - this should *not* initiate navigating the main
// frame to |dragged_url| (because this would be a forbidden, web->file
// navigation).
ASSERT_TRUE(SimulateDropInRightFrame());
// Verify that the right frame is still responsive (this is a regression test
// for https://crbug.com/1003169.
ASSERT_TRUE(GetRightFrame()->GetProcess()->IsInitializedAndNotDead());
EXPECT_EQ(123, content::EvalJs(GetRightFrame(), "123"));
// Verify that the history remains unchanged.
EXPECT_NE(dragged_url,
web_contents()->GetPrimaryMainFrame()->GetLastCommittedURL());
EXPECT_EQ(initial_history_count, controller.GetEntryCount());
}
// 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) {
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateLeftFrame(frame_site, "image_source.html"));
// Setup test expectations.
DOMDragEventVerifier expected_dom_event_data;
expected_dom_event_data.set_expected_client_position("(55, 50)");
expected_dom_event_data.set_expected_drop_effect("none");
// (dragstart event handler in image_source.html is asking for "copy" only).
expected_dom_event_data.set_expected_effect_allowed("copy");
expected_dom_event_data.set_expected_page_position("(55, 50)");
// The dragstart event can have different mime types to the following events.
// It is created by the renderer with the original DataTransfer object which
// is then sent to the browser which initiates subsequent events.
// The dragstart mime types will always include 'File', but access to the file
// will not be allowed if the image is cross-origin (getAsFile() is null).
// When the browser receives the DataTransfer, it copies data into
// OSExchangeData and uses this for following events. Copying in text/html
// will also populate text/plain. The File object may or may not be included
// in following events depending on whether the image is cross-origin, and
// whether the drop target is to a different page.
expected_dom_event_data.set_expected_mime_types(
"Files,text/html,text/uri-list");
// Start the drag in the left frame.
DragStartWaiter drag_start_waiter(web_contents());
drag_start_waiter.SuppressPassingStartDragFurther();
DOMDragEventWaiter dragstart_event_waiter("dragstart", GetLeftFrame());
EXPECT_TRUE(SimulateMouseDownAndDragStartInLeftFrame());
// Verify Javascript event data.
{
std::string dragstart_event;
EXPECT_TRUE(
dragstart_event_waiter.WaitForNextMatchingEvent(&dragstart_event));
EXPECT_THAT(dragstart_event, expected_dom_event_data.Matches());
}
// Verify data being passed to the OS.
{
std::optional<url::Origin> source_origin;
std::string text;
std::string html;
int operation = 0;
gfx::Point location_inside_web_contents;
drag_start_waiter.WaitUntilDragStart(&source_origin, &text, &html,
&operation,
&location_inside_web_contents);
ASSERT_TRUE(source_origin.has_value());
EXPECT_EQ(https_test_server()->GetOrigin(frame_site), source_origin);
EXPECT_EQ(https_test_server()->GetURL(frame_site,
"/drag_and_drop/cors-allowed.jpg"),
text);
EXPECT_THAT(
html,
testing::MatchesRegex(
R"(<img .*src="https://.*/drag_and_drop/cors-allowed.jpg">)"));
EXPECT_EQ(expected_location_of_drag_start_in_left_frame(),
location_inside_web_contents);
EXPECT_EQ(ui::DragDropTypes::DRAG_COPY, operation);
}
// Try to leave everything in a clean state.
SimulateMouseUp();
}
#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 \
DISABLED_DragSameOriginImageBetweenFrames
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// Failing to receive final drop event on linux crbug.com/1268407.
// TODO(crbug.com/442927728): Fix failing test on ChromeOS
#define MAYBE_DragSameOriginImageBetweenFrames \
DISABLED_DragSameOriginImageBetweenFrames
#else
#define MAYBE_DragSameOriginImageBetweenFrames 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 {
bool expect_image_accessible = false;
DOMDragEventVerifier expected_dom_event_data;
std::unique_ptr<DOMDragEventWaiter> dragstart_event_waiter;
std::unique_ptr<DOMDragEventWaiter> drop_event_waiter;
std::unique_ptr<DOMDragEventWaiter> dragend_event_waiter;
std::unique_ptr<DOMDragEventCounter> left_frame_events_counter;
std::unique_ptr<DOMDragEventCounter> right_frame_events_counter;
};
// 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) {
// TODO (crbug/1521094): Test fails since 2023 refresh.
if (std::get<1>(GetParam()) > 1.5) {
GTEST_SKIP();
}
DragImageBetweenFrames_Start(/*image_same_origin=*/true,
/*image_crossorigin_attr=*/false);
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DragCorsSameOriginImageBetweenFrames \
DISABLED_DragCorsSameOriginImageBetweenFrames
#else
#define MAYBE_DragCorsSameOriginImageBetweenFrames \
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) {
// TODO (crbug/1521094): Test fails since 2023 refresh.
if (std::get<1>(GetParam()) > 1.5) {
GTEST_SKIP();
}
DragImageBetweenFrames_Start(/*image_same_origin=*/false,
/*image_crossorigin_attr=*/true);
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DragCrossOriginImageBetweenFrames \
DISABLED_DragCrossOriginImageBetweenFrames
#else
#define MAYBE_DragCrossOriginImageBetweenFrames \
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) {
// TODO (crbug/1521094): Test fails since 2023 refresh.
if (std::get<1>(GetParam()) > 1.5) {
GTEST_SKIP();
}
DragImageBetweenFrames_Start(/*image_same_origin=*/false,
/*image_crossorigin_attr=*/false);
}
void DragAndDropBrowserTest::DragImageBetweenFrames_Start(
bool image_same_origin,
bool image_crossorigin_attr) {
// Note that drag and drop will not expose data across cross-site frames on
// the same page - this is why the same |frame_site| is used below both for
// the left and the right frame. See also https://crbug.com/59081.
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateLeftFrame(frame_site,
image_same_origin ? frame_site : "c.test",
image_crossorigin_attr, "image_source.html"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "drop_target.html"));
// Setup test expectations.
DragAndDropBrowserTest::DragImageBetweenFrames_TestState state;
state.expect_image_accessible = image_same_origin || image_crossorigin_attr;
state.left_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetLeftFrame());
state.right_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetRightFrame());
state.expected_dom_event_data.set_expected_client_position("(55, 50)");
state.expected_dom_event_data.set_expected_drop_effect("none");
// (dragstart event handler in image_source.html is asking for "copy" only).
state.expected_dom_event_data.set_expected_effect_allowed("copy");
state.expected_dom_event_data.set_expected_file_names(
state.expect_image_accessible ? "cors-allowed.jpg" : "");
state.expected_dom_event_data.set_expected_mime_types(
"Files,text/html,text/uri-list");
state.expected_dom_event_data.set_expected_page_position("(55, 50)");
// Start the drag in the left frame.
DragStartWaiter drag_start_waiter(web_contents());
drag_start_waiter.PostTaskWhenDragStarts(
base::BindOnce(&DragAndDropBrowserTest::DragImageBetweenFrames_Step2,
base::Unretained(this), base::Unretained(&state)));
state.dragstart_event_waiter =
std::make_unique<DOMDragEventWaiter>("dragstart", GetLeftFrame());
EXPECT_TRUE(SimulateMouseDownAndDragStartInLeftFrame());
// The next step of the test (DragImageBetweenFrames_Step2) runs inside the
// nested drag-and-drop message loop - the call below won't return until the
// drag-and-drop has already ended.
drag_start_waiter.WaitUntilDragStart();
DragImageBetweenFrames_Step3(&state);
}
void DragAndDropBrowserTest::DragImageBetweenFrames_Step2(
DragAndDropBrowserTest::DragImageBetweenFrames_TestState* state) {
// Verify dragstart DOM event.
{
std::string dragstart_event;
EXPECT_TRUE(state->dragstart_event_waiter->WaitForNextMatchingEvent(
&dragstart_event));
EXPECT_THAT(dragstart_event, state->expected_dom_event_data.Matches());
state->dragstart_event_waiter.reset();
// Only a single "dragstart" should have fired in the left frame since the
// start of the test. We also allow any number of "dragover" events.
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragstart"));
EXPECT_EQ(0, state->left_frame_events_counter->GetNumberOfReceivedEvents(
{"dragleave", "dragenter", "drop", "dragend"}));
// No events should have fired in the right frame yet.
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragover", "drop",
"dragend"}));
}
// While dragging, move mouse within the left frame.
// Without this extra mouse move we wouldn't get a dragleave event later on.
ASSERT_TRUE(SimulateMouseMoveToLeftFrame());
// While dragging, move mouse from the left into the right frame.
// This should trigger dragleave and dragenter events.
{
DOMDragEventWaiter dragleave_event_waiter("dragleave", GetLeftFrame());
DOMDragEventWaiter dragenter_event_waiter("dragenter", GetRightFrame());
state->left_frame_events_counter->Reset();
state->right_frame_events_counter->Reset();
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
{ // Verify dragleave DOM event.
std::string dragleave_event;
state->expected_dom_event_data.set_expected_client_position("(355, 150)");
state->expected_dom_event_data.set_expected_page_position("(355, 150)");
state->expected_dom_event_data.set_expected_file_names("");
state->expected_dom_event_data.set_expected_drop_effect("none");
state->expected_dom_event_data.set_expected_mime_types(
state->expect_image_accessible
? "Files,text/html,text/plain,text/uri-list"
: "text/html,text/plain,text/uri-list");
EXPECT_TRUE(
dragleave_event_waiter.WaitForNextMatchingEvent(&dragleave_event));
EXPECT_THAT(dragleave_event, state->expected_dom_event_data.Matches());
}
{ // Verify dragenter DOM event.
std::string dragenter_event;
// Update expected event coordinates after SimulateMouseMoveToRightFrame
// (these coordinates are relative to the right frame).
state->expected_dom_event_data.set_expected_client_position("(155, 150)");
state->expected_dom_event_data.set_expected_page_position("(155, 150)");
state->expected_dom_event_data.set_expected_drop_effect("copy");
EXPECT_TRUE(
dragenter_event_waiter.WaitForNextMatchingEvent(&dragenter_event));
EXPECT_THAT(dragenter_event, state->expected_dom_event_data.Matches());
}
// Note that ash (unlike aura/x11) will not fire dragover event in response
// to the same mouse event that triggered a dragenter. Because of that, we
// postpone dragover testing until the next test step below. See
// implementation of ash::DragDropController::DragUpdate for details.
}
// Move the mouse twice in the right frame. The 1st move will ensure that
// allowed operations communicated by the renderer will be stored in
// WebContentsViewAura::current_drag_data_. The 2nd move will ensure that this
// gets copied into DesktopDragDropClientAuraX11::negotiated_operation_.
for (int i = 0; i < 2; i++) {
DOMDragEventWaiter dragover_event_waiter("dragover", GetRightFrame());
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
{ // Verify dragover DOM event.
std::string dragover_event;
EXPECT_TRUE(
dragover_event_waiter.WaitForNextMatchingEvent(&dragover_event));
EXPECT_THAT(dragover_event, state->expected_dom_event_data.Matches());
}
}
// Only a single "dragleave" should have fired in the left frame since the
// last checkpoint. We also allow any number of "dragover" events.
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragleave"));
EXPECT_EQ(0, state->left_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragenter", "drop", "dragend"}));
// A single "dragenter" + at least one "dragover" event should have fired in
// the right frame since the last checkpoint.
EXPECT_EQ(1, state->right_frame_events_counter->GetNumberOfReceivedEvents(
"dragenter"));
EXPECT_LE(1, state->right_frame_events_counter->GetNumberOfReceivedEvents(
"dragover"));
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "drop", "dragend"}));
// Release the mouse button to end the drag.
state->drop_event_waiter =
std::make_unique<DOMDragEventWaiter>("drop", GetRightFrame());
state->dragend_event_waiter =
std::make_unique<DOMDragEventWaiter>("dragend", GetLeftFrame());
state->left_frame_events_counter->Reset();
state->right_frame_events_counter->Reset();
SimulateMouseUp();
// The test will continue in DragImageBetweenFrames_Step3.
}
void DragAndDropBrowserTest::DragImageBetweenFrames_Step3(
DragAndDropBrowserTest::DragImageBetweenFrames_TestState* state) {
// Verify drop DOM event.
{
// Create separate expectations for drop event since dropEffect changes to
// "copy"
DOMDragEventVerifier expected_drop_event_data;
expected_drop_event_data.set_expected_client_position("(155, 150)");
expected_drop_event_data.set_expected_drop_effect("copy");
expected_drop_event_data.set_expected_effect_allowed("copy");
expected_drop_event_data.set_expected_page_position("(155, 150)");
// File contents is sent in drop event.
expected_drop_event_data.set_expected_file_names(
state->expect_image_accessible ? "cors-allowed.jpg" : "");
expected_drop_event_data.set_expected_mime_types(
state->expect_image_accessible
? "Files,text/html,text/plain,text/uri-list"
: "text/html,text/plain,text/uri-list");
std::string drop_event;
EXPECT_TRUE(
state->drop_event_waiter->WaitForNextMatchingEvent(&drop_event));
state->drop_event_waiter.reset();
EXPECT_THAT(drop_event, expected_drop_event_data.Matches());
}
// Verify dragend DOM event.
{
// Different values of DataTransfer.dropEffect is observed and is
// being tracked by https://crbug.com/1470718.
// Different values of DataTransfer.types is seen due to
// https://crbug.com/394955. This causes certain File objects to be
// mapped to text/plain in `DataObject::ToWebDragData()` and thus
// text/plain is seen in "dragleave", "dragenter", "dragover" and "drop"
// events. While dragend doesn't use WebDragData object and that is why
// text/plain is not seen in this event.
state->expected_dom_event_data.set_expected_drop_effect("copy");
state->expected_dom_event_data.set_expected_mime_types(
"Files,text/html,text/uri-list");
// TODO: https://crbug.com/686136: dragEnd coordinates for non-OOPIF
// scenarios are currently broken.
state->expected_dom_event_data.set_expected_client_position(
"<no expectation>");
state->expected_dom_event_data.set_expected_page_position(
"<no expectation>");
// File contents is not sent in dragend.
state->expected_dom_event_data.set_expected_file_names("");
std::string dragend_event;
EXPECT_TRUE(
state->dragend_event_waiter->WaitForNextMatchingEvent(&dragend_event));
state->dragend_event_waiter.reset();
EXPECT_THAT(dragend_event, state->expected_dom_event_data.Matches());
}
// Only a single "dragend" should have fired in the left frame since the last
// checkpoint.
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragend"));
EXPECT_EQ(0,
state->left_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragover", "drop"}));
// A single "drop" + possibly some "dragover" events should have fired in the
// right frame since the last checkpoint.
EXPECT_EQ(
1, state->right_frame_events_counter->GetNumberOfReceivedEvents("drop"));
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragend"}));
}
// 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/40876472): Enable on ChromeOS once flakiness is fixed.
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
#define MAYBE_DragImageFromDisappearingFrame \
DISABLED_DragImageFromDisappearingFrame
#else
#define MAYBE_DragImageFromDisappearingFrame 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 {
DOMDragEventVerifier expected_dom_event_data;
std::unique_ptr<DOMDragEventWaiter> drop_event_waiter;
};
// 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) {
// TODO (crbug/1521094): Test fails since 2023 refresh.
if (std::get<1>(GetParam()) > 1.5) {
GTEST_SKIP();
}
// Load the test page.
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateLeftFrame(frame_site, "image_source.html"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "drop_target.html"));
// Setup test expectations.
DragAndDropBrowserTest::DragImageFromDisappearingFrame_TestState state;
state.expected_dom_event_data.set_expected_drop_effect("copy");
// (dragstart event handler in image_source.html is asking for "copy" only).
state.expected_dom_event_data.set_expected_effect_allowed("copy");
state.expected_dom_event_data.set_expected_mime_types(
"Files,text/html,text/plain,text/uri-list");
state.expected_dom_event_data.set_expected_client_position("(155, 150)");
state.expected_dom_event_data.set_expected_page_position("(155, 150)");
// Start the drag in the left frame.
DragStartWaiter drag_start_waiter(web_contents());
drag_start_waiter.PostTaskWhenDragStarts(base::BindOnce(
&DragAndDropBrowserTest::DragImageFromDisappearingFrame_Step2,
base::Unretained(this), base::Unretained(&state)));
EXPECT_TRUE(SimulateMouseDownAndDragStartInLeftFrame());
// The next step of the test (DragImageFromDisappearingFrame_Step2) runs
// inside the nested drag-and-drop message loop - the call below won't return
// until the drag-and-drop has already ended.
drag_start_waiter.WaitUntilDragStart();
DragImageFromDisappearingFrame_Step3(&state);
}
void DragAndDropBrowserTest::DragImageFromDisappearingFrame_Step2(
DragAndDropBrowserTest::DragImageFromDisappearingFrame_TestState* state) {
// Delete the left frame in an attempt to repro https://crbug.com/670123.
content::RenderFrameDeletedObserver frame_deleted_observer(GetLeftFrame());
ASSERT_TRUE(ExecJs(web_contents()->GetPrimaryMainFrame(),
"frame = document.getElementById('left');\n"
"frame.parentNode.removeChild(frame);\n"));
frame_deleted_observer.WaitUntilDeleted();
// While dragging, move mouse from the left into the right frame.
// This should trigger dragleave and dragenter events.
{
DOMDragEventWaiter dragenter_event_waiter("dragenter", GetRightFrame());
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
{ // Verify dragenter DOM event.
std::string dragenter_event;
EXPECT_TRUE(
dragenter_event_waiter.WaitForNextMatchingEvent(&dragenter_event));
EXPECT_THAT(dragenter_event, state->expected_dom_event_data.Matches());
}
// Note that ash (unlike aura/x11) will not fire dragover event in response
// to the same mouse event that triggered a dragenter. Because of that, we
// postpone dragover testing until the next test step below. See
// implementation of ash::DragDropController::DragUpdate for details.
}
// Move the mouse twice in the right frame. The 1st move will ensure that
// allowed operations communicated by the renderer will be stored in
// WebContentsViewAura::current_drag_data_. The 2nd move will ensure that this
// gets copied into DesktopDragDropClientAuraX11::negotiated_operation_.
for (int i = 0; i < 2; i++) {
DOMDragEventWaiter dragover_event_waiter("dragover", GetRightFrame());
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
{ // Verify dragover DOM event.
std::string dragover_event;
EXPECT_TRUE(
dragover_event_waiter.WaitForNextMatchingEvent(&dragover_event));
EXPECT_THAT(dragover_event, state->expected_dom_event_data.Matches());
}
}
// Release the mouse button to end the drag.
state->drop_event_waiter =
std::make_unique<DOMDragEventWaiter>("drop", GetRightFrame());
SimulateMouseUp();
// The test will continue in DragImageFromDisappearingFrame_Step3.
}
void DragAndDropBrowserTest::DragImageFromDisappearingFrame_Step3(
DragAndDropBrowserTest::DragImageFromDisappearingFrame_TestState* state) {
// Verify drop DOM event.
{
// Create separate expectations for drop event since dropEffect changes to
// "copy"
DOMDragEventVerifier expected_drop_event_data;
expected_drop_event_data.set_expected_client_position("(155, 150)");
expected_drop_event_data.set_expected_drop_effect("copy");
expected_drop_event_data.set_expected_effect_allowed("copy");
expected_drop_event_data.set_expected_mime_types(
"Files,text/html,text/plain,text/uri-list");
expected_drop_event_data.set_expected_page_position("(155, 150)");
std::string drop_event;
EXPECT_TRUE(
state->drop_event_waiter->WaitForNextMatchingEvent(&drop_event));
state->drop_event_waiter.reset();
EXPECT_THAT(drop_event, expected_drop_event_data.Matches());
}
}
// 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 DISABLED_CrossSiteDrag
#elif BUILDFLAG(IS_CHROMEOS)
#define MAYBE_CrossSiteDrag DISABLED_CrossSiteDrag
#else
#define MAYBE_CrossSiteDrag 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 {
std::unique_ptr<DOMDragEventWaiter> dragend_event_waiter;
std::unique_ptr<DOMDragEventCounter> left_frame_events_counter;
std::unique_ptr<DOMDragEventCounter> right_frame_events_counter;
};
// 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) {
std::string left_frame_site = "c.test"; // Always cross-site VS main frame.
std::string right_frame_site =
use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateLeftFrame(left_frame_site, "image_source.html"));
ASSERT_TRUE(NavigateRightFrame(right_frame_site, "drop_target.html"));
// Setup test expectations.
DragAndDropBrowserTest::CrossSiteDrag_TestState state;
state.left_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetLeftFrame());
state.right_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetRightFrame());
// Start the drag in the left frame.
DragStartWaiter drag_start_waiter(web_contents());
drag_start_waiter.PostTaskWhenDragStarts(
base::BindOnce(&DragAndDropBrowserTest::CrossSiteDrag_Step2,
base::Unretained(this), base::Unretained(&state)));
EXPECT_TRUE(SimulateMouseDownAndDragStartInLeftFrame());
// The next step of the test (CrossSiteDrag_Step2) runs inside the
// nested drag-and-drop message loop - the call below won't return until the
// drag-and-drop has already ended.
drag_start_waiter.WaitUntilDragStart();
CrossSiteDrag_Step3(&state);
}
void DragAndDropBrowserTest::CrossSiteDrag_Step2(
DragAndDropBrowserTest::CrossSiteDrag_TestState* state) {
// While "dragleave" and "drop" events are not expected in this test, we
// simulate extra mouse operations for consistency with
// DragImageBetweenFrames_Step2.
ASSERT_TRUE(SimulateMouseMoveToLeftFrame());
for (int i = 0; i < 3; i++) {
content::DOMMessageQueue dom_message_queue(web_contents());
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
// No events are expected from the right frame, so we can't wait for a
// dragover event here. Still - we do want to wait until the right frame
// has had a chance to process any previous browser IPCs, so that in case
// there *is* a bug and a dragover event *does* happen, we won't terminate
// the test before the event has had a chance to be reported back to the
// browser.
std::string expected_response = base::StringPrintf("\"i%d\"", i);
GetRightFrame()->ExecuteJavaScriptWithUserGestureForTests(
base::UTF8ToUTF16(base::StringPrintf(
"domAutomationController.send(%s);", expected_response.c_str())),
base::NullCallback(), content::ISOLATED_WORLD_ID_GLOBAL);
// Wait until our response comes back (it might be mixed with responses
// carrying events that are sent by event_monitoring.js).
std::string actual_response;
do {
ASSERT_TRUE(dom_message_queue.WaitForMessage(&actual_response));
} while (actual_response != expected_response);
}
// Release the mouse button to end the drag.
state->dragend_event_waiter =
std::make_unique<DOMDragEventWaiter>("dragend", GetLeftFrame());
SimulateMouseUp();
// The test will continue in CrossSiteDrag_Step3.
}
void DragAndDropBrowserTest::CrossSiteDrag_Step3(
DragAndDropBrowserTest::CrossSiteDrag_TestState* state) {
EXPECT_TRUE(state->dragend_event_waiter->WaitForNextMatchingEvent(nullptr));
// Since the start of the test the left frame should have seen a single
// "dragstart", and a "dragend" event (and possibly a "dragleave" and some
// "dragover" events).
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragstart"));
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragend"));
EXPECT_EQ(
0, state->left_frame_events_counter->GetNumberOfReceivedEvents("drop"));
// No events should have fired in the right frame, because it is cross-site
// from the source of the drag. This is the essence of this test.
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragover", "drop",
"dragend"}));
}
// 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 DISABLED_CrossNavCrossSiteDrag
#else
#define MAYBE_CrossNavCrossSiteDrag CrossNavCrossSiteDrag
#endif
struct DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState {
// Tracks events in the left frame of the initial site.
std::unique_ptr<DOMDragEventCounter> left_frame_events_counter;
// Tracks events in the main frame of the navigated-to site.
std::unique_ptr<DOMDragEventCounter> post_nav_events_counter;
};
// 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) {
std::string left_frame_site = "b.test"; // Always cross-site VS main frame.
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateLeftFrame(left_frame_site, "image_source.html"));
// Setup test expectations.
DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState state;
state.left_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetLeftFrame());
// Start the drag in the left frame.
DragStartWaiter drag_start_waiter(web_contents());
drag_start_waiter.PostTaskWhenDragStarts(
base::BindOnce(&DragAndDropBrowserTest::CrossNavCrossSiteDrag_Step2,
base::Unretained(this), base::Unretained(&state)));
EXPECT_TRUE(SimulateMouseDownAndDragStartInLeftFrame());
// The next step of the test (CrossNavCrossSiteDrag_Step2) runs inside the
// nested drag-and-drop message loop - the call below won't return until the
// drag-and-drop has already ended.
drag_start_waiter.WaitUntilDragStart();
CrossNavCrossSiteDrag_Step3(&state);
}
void DragAndDropBrowserTest::CrossNavCrossSiteDrag_Step2(
DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState* state) {
// Drag has started; navigate the main frame and make sure the RVH has changed
// (to validate this test is working as intended).
content::RenderViewHost* rvh_before_nav =
web_contents()->GetPrimaryMainFrame()->GetRenderViewHost();
ASSERT_TRUE(NavigateMainFrame("c.test", "/drag_and_drop/drop_target.html"));
EXPECT_NE(rvh_before_nav,
web_contents()->GetPrimaryMainFrame()->GetRenderViewHost());
state->post_nav_events_counter = std::make_unique<DOMDragEventCounter>(
web_contents()->GetPrimaryMainFrame());
// While "dragleave" and "drop" events are not expected in this test, we
// simulate extra mouse operations for consistency with
// DragImageBetweenFrames_Step2.
ASSERT_TRUE(SimulateMouseMove(gfx::Point(15, 15)));
for (int i = 0; i < 3; i++) {
content::DOMMessageQueue dom_message_queue(web_contents());
ASSERT_TRUE(SimulateMouseMove(gfx::Point(15, 16 + i)));
// No events are expected from the right frame, so we can't wait for a
// dragover event here. Still - we do want to wait until the right frame
// has had a chance to process any previous browser IPCs, so that in case
// there *is* a bug and a dragover event *does* happen, we won't terminate
// the test before the event has had a chance to be reported back to the
// browser.
std::string expected_response = base::StringPrintf("\"i%d\"", i);
web_contents()
->GetPrimaryMainFrame()
->ExecuteJavaScriptWithUserGestureForTests(
base::UTF8ToUTF16(
base::StringPrintf("domAutomationController.send(%s);",
expected_response.c_str())),
base::NullCallback(), content::ISOLATED_WORLD_ID_GLOBAL);
// Wait until our response comes back (it might be mixed with responses
// carrying events that are sent by event_monitoring.js).
std::string actual_response;
do {
ASSERT_TRUE(dom_message_queue.WaitForMessage(&actual_response));
} while (actual_response != expected_response);
}
// Release the mouse button to end the drag.
SimulateMouseUp();
// The test will continue in CrossNavCrossSiteDrag_Step3.
}
void DragAndDropBrowserTest::CrossNavCrossSiteDrag_Step3(
DragAndDropBrowserTest::CrossNavCrossSiteDrag_TestState* state) {
// Since the start of the test the left frame should have seen a single
// "dragstart". The "dragend" may be missing since the navigation happened in
// the middle of the drag.
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragstart"));
EXPECT_EQ(
0, state->left_frame_events_counter->GetNumberOfReceivedEvents("drop"));
// No events should have fired in the site, because it is cross-site
// from the source of the drag but is in the same tab. This is the essence of
// this test.
EXPECT_EQ(0, state->post_nav_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragover", "drop",
"dragend"}));
}
// 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) || BUILDFLAG(IS_LINUX)
// TODO(crbug.com/442927728): Fix failing test on Linux and ChromeOS
// https://crbug.com/1393605: Flaky at ChromeOS ASAN and Debug builds
#define MAYBE_CrossTabDrag DISABLED_CrossTabDrag
#else
#define MAYBE_CrossTabDrag 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 {
DOMDragEventVerifier expected_dom_event_data;
std::unique_ptr<DOMDragEventWaiter> dragstart_event_waiter;
std::unique_ptr<DOMDragEventWaiter> drop_event_waiter;
std::unique_ptr<DOMDragEventWaiter> dragend_event_waiter;
std::unique_ptr<DOMDragEventCounter> left_frame_events_counter;
std::unique_ptr<DOMDragEventCounter> right_frame_events_counter;
};
// 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) {
// TODO (crbug/1521094): Test fails since 2023 refresh.
if (std::get<1>(GetParam()) > 1.5) {
GTEST_SKIP();
}
std::string right_frame_site =
use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateLeftFrame("c.test", "image_source.html"));
// Add a new tab navigated to the test page, and navigate the right subframe.
GURL url = https_test_server()->GetURL("a.test", kTestPagePath);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
ASSERT_EQ(browser()->tab_strip_model()->count(), 2);
content::WebContents* first_contents =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::WebContents* second_contents =
browser()->tab_strip_model()->GetWebContentsAt(1);
ASSERT_TRUE(NavigateNamedFrame("right", right_frame_site, "drop_target.html",
second_contents));
// Setup test expectations.
DragAndDropBrowserTest::CrossTabDrag_TestState state;
state.left_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetLeftFrame(first_contents));
state.right_frame_events_counter =
std::make_unique<DOMDragEventCounter>(GetRightFrame(second_contents));
state.expected_dom_event_data.set_expected_client_position("(55, 50)");
state.expected_dom_event_data.set_expected_drop_effect("copy");
// (dragstart event handler in image_source.html is asking for "copy" only).
state.expected_dom_event_data.set_expected_effect_allowed("copy");
state.expected_dom_event_data.set_expected_mime_types(
"Files,text/html,text/plain,text/uri-list");
state.expected_dom_event_data.set_expected_page_position("(55, 50)");
// Make the first tab active, then start the drag in the left frame.
browser()->tab_strip_model()->ActivateTabAt(0);
DragStartWaiter drag_start_waiter(web_contents());
drag_start_waiter.PostTaskWhenDragStarts(
base::BindOnce(&DragAndDropBrowserTest::CrossTabDrag_Step2,
base::Unretained(this), base::Unretained(&state)));
state.dragstart_event_waiter = std::make_unique<DOMDragEventWaiter>(
"dragstart", GetLeftFrame(first_contents));
EXPECT_TRUE(SimulateMouseDownAndDragStartInLeftFrame());
// The next step of the test (CrossTabDrag_Step2) runs inside the
// nested drag-and-drop message loop - the call below won't return until the
// drag-and-drop has already ended.
drag_start_waiter.WaitUntilDragStart();
CrossTabDrag_Step3(&state);
}
void DragAndDropBrowserTest::CrossTabDrag_Step2(
DragAndDropBrowserTest::CrossTabDrag_TestState* state) {
browser()->tab_strip_model()->ActivateTabAt(0);
content::WebContents* first_contents =
browser()->tab_strip_model()->GetWebContentsAt(0);
content::WebContents* second_contents =
browser()->tab_strip_model()->GetWebContentsAt(1);
// Verify dragstart DOM event.
{
std::string dragstart_event;
EXPECT_TRUE(state->dragstart_event_waiter->WaitForNextMatchingEvent(
&dragstart_event));
state->dragstart_event_waiter.reset();
// Only a single "dragstart" should have fired in the left frame since the
// start of the test. We also allow any number of "dragover" events.
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragstart"));
EXPECT_EQ(0, state->left_frame_events_counter->GetNumberOfReceivedEvents(
{"dragleave", "dragenter", "drop", "dragend"}));
// No events should have fired in the right frame yet.
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragover", "drop",
"dragend"}));
}
// While dragging, move mouse from the left into the right frame.
// This should trigger a dragenter event.
{
DOMDragEventWaiter dragenter_event_waiter("dragenter",
GetRightFrame(second_contents));
state->left_frame_events_counter->Reset();
state->right_frame_events_counter->Reset();
// Switch tabs before moving mouse to right frame. This simulates the case
// where a drag is started, the user pressed alt+tab, then completes the
// drop.
browser()->tab_strip_model()->ActivateTabAt(1);
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
{ // Verify dragenter DOM event.
std::string dragenter_event;
// Update expected event coordinates after SimulateMouseMoveToRightFrame
// (these coordinates are relative to the right frame).
state->expected_dom_event_data.set_expected_client_position("(155, 150)");
state->expected_dom_event_data.set_expected_page_position("(155, 150)");
EXPECT_TRUE(
dragenter_event_waiter.WaitForNextMatchingEvent(&dragenter_event));
EXPECT_THAT(dragenter_event, state->expected_dom_event_data.Matches());
}
// Note that ash (unlike aura/x11) will not fire dragover event in response
// to the same mouse event that triggered a dragenter. Because of that, we
// postpone dragover testing until the next test step below. See
// implementation of ash::DragDropController::DragUpdate for details.
}
// Move the mouse twice in the right frame. The 1st move will ensure that
// allowed operations communicated by the renderer will be stored in
// WebContentsViewAura::current_drag_data_. The 2nd move will ensure that this
// gets copied into DesktopDragDropClientAuraX11::negotiated_operation_.
for (int i = 0; i < 2; i++) {
DOMDragEventWaiter dragover_event_waiter("dragover", GetRightFrame());
ASSERT_TRUE(SimulateMouseMoveToRightFrame());
{ // Verify dragover DOM event.
std::string dragover_event;
EXPECT_TRUE(
dragover_event_waiter.WaitForNextMatchingEvent(&dragover_event));
EXPECT_THAT(dragover_event, state->expected_dom_event_data.Matches());
}
}
// We allow any number of "dragover" events, but should not have any of the
// listed events.
EXPECT_EQ(0, state->left_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragenter", "drop", "dragend"}));
// A single "dragenter" + at least one "dragover" event should have fired in
// the right frame since the last checkpoint.
EXPECT_EQ(1, state->right_frame_events_counter->GetNumberOfReceivedEvents(
"dragenter"));
EXPECT_LE(1, state->right_frame_events_counter->GetNumberOfReceivedEvents(
"dragover"));
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "drop", "dragend"}));
// Release the mouse button to end the drag.
state->drop_event_waiter = std::make_unique<DOMDragEventWaiter>(
"drop", GetRightFrame(second_contents));
state->dragend_event_waiter = std::make_unique<DOMDragEventWaiter>(
"dragend", GetLeftFrame(first_contents));
state->left_frame_events_counter->Reset();
state->right_frame_events_counter->Reset();
SimulateMouseUp();
// The test will continue in CrossTabDrag_Step3.
}
void DragAndDropBrowserTest::CrossTabDrag_Step3(
DragAndDropBrowserTest::CrossTabDrag_TestState* state) {
// Verify drop DOM event.
{
// Setup drop event expectations (dropEffect changes to "copy" during drop).
DOMDragEventVerifier expected_drop_event_data;
expected_drop_event_data.set_expected_client_position("(155, 150)");
expected_drop_event_data.set_expected_drop_effect("copy");
expected_drop_event_data.set_expected_effect_allowed("copy");
expected_drop_event_data.set_expected_mime_types(
"Files,text/html,text/plain,text/uri-list");
expected_drop_event_data.set_expected_page_position("(155, 150)");
std::string drop_event;
EXPECT_TRUE(
state->drop_event_waiter->WaitForNextMatchingEvent(&drop_event));
state->drop_event_waiter.reset();
EXPECT_THAT(drop_event, expected_drop_event_data.Matches());
}
// Verify dragend DOM event.
{
// Different values of DataTransfer.dropEffect is observed and is
// being tracked by https://crbug.com/1470718.
// Different values of DataTransfer.types is seen due to
// https://crbug.com/394955. This causes certain File objects to be
// mapped to text/plain in `DataObject::ToWebDragData()` and thus
// text/plain is seen in "dragleave", "dragenter", "dragover" and "drop"
// events. While dragend doesn't use WebDragData object and that is why
// text/plain is not seen in this event.
state->expected_dom_event_data.set_expected_drop_effect("copy");
state->expected_dom_event_data.set_expected_mime_types(
"Files,text/html,text/uri-list");
// TODO: https://crbug.com/686136: dragEnd coordinates for non-OOPIF
// scenarios are currently broken.
state->expected_dom_event_data.set_expected_client_position(
"<no expectation>");
state->expected_dom_event_data.set_expected_page_position(
"<no expectation>");
std::string dragend_event;
EXPECT_TRUE(
state->dragend_event_waiter->WaitForNextMatchingEvent(&dragend_event));
state->dragend_event_waiter.reset();
EXPECT_THAT(dragend_event, state->expected_dom_event_data.Matches());
}
// Only a single "dragend" should have fired in the left frame since the last
// checkpoint.
EXPECT_EQ(1, state->left_frame_events_counter->GetNumberOfReceivedEvents(
"dragend"));
EXPECT_EQ(0,
state->left_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragover", "drop"}));
// A single "drop" + possibly some "dragover" events should have fired in the
// right frame since the last checkpoint.
EXPECT_EQ(
1, state->right_frame_events_counter->GetNumberOfReceivedEvents("drop"));
EXPECT_EQ(0, state->right_frame_events_counter->GetNumberOfReceivedEvents(
{"dragstart", "dragleave", "dragenter", "dragend"}));
}
// 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) {
// Reposition the window so that the root window coordinate space and the
// screen coordinate space are clearly distinct. Otherwise this test would
// be inconclusive.
// In addition to offsetting the window, use a small window size to avoid
// rejection of the new bounds by the system.
browser()->window()->SetBounds(gfx::Rect(200, 100, 700, 500));
EXPECT_TRUE(base::test::RunUntil([&]() {
return browser()->window()->GetBounds().origin() == gfx::Point(200, 100);
}));
std::string frame_site = use_cross_site_subframe() ? "b.test" : "a.test";
ASSERT_TRUE(NavigateToTestPage("a.test"));
ASSERT_TRUE(NavigateRightFrame(frame_site, "drop_target.html"));
const gfx::Point screen_position = GetMiddleOfRightFrameInScreenCoords();
DOMDragEventVerifier expected_dom_event_data;
expected_dom_event_data.set_expected_client_position("(155, 150)");
expected_dom_event_data.set_expected_screen_position(
base::StringPrintf("(%d, %d)", screen_position.x(), screen_position.y()));
DOMDragEventWaiter dragover_waiter("dragover", GetRightFrame());
ASSERT_TRUE(SimulateDragEnterToRightFrame("Dragged test text"));
std::string dragover_event;
ASSERT_TRUE(dragover_waiter.WaitForNextMatchingEvent(&dragover_event));
EXPECT_THAT(dragover_event, expected_dom_event_data.Matches());
}
// 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.
#if BUILDFLAG(IS_CHROMEOS)
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 = {1.0};
#endif
INSTANTIATE_TEST_SUITE_P(
SameSiteSubframe,
DragAndDropBrowserTest,
::testing::Combine(::testing::Values(false),
::testing::ValuesIn(ui_scaling_factors)));
INSTANTIATE_TEST_SUITE_P(
CrossSiteSubframe,
DragAndDropBrowserTest,
::testing::Combine(::testing::Values(true),
::testing::ValuesIn(ui_scaling_factors)));
#if BUILDFLAG(IS_CHROMEOS)
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
// TODO(crbug.com/441134573): Fix and reenable the test.
IN_PROC_BROWSER_TEST_F(DragAndDropBrowserTestNoParam,
DISABLED_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)
} // namespace chrome