blob: 6fe365d46468dedbecc8806344ccddfb1301af13 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/web_contents/web_contents_view_aura.h"
#include <memory>
#include <optional>
#include <string>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/drag_drop_client.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/test/window_test_api.h"
#include "ui/aura/window.h"
#include "ui/base/data_transfer_policy/data_transfer_endpoint.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/base/ozone_buildflags.h"
#include "ui/display/display_switches.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "url/origin.h"
#if BUILDFLAG(IS_WIN)
#include "ui/base/dragdrop/os_exchange_data_provider_win.h"
#endif
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE_X11)
#include "ui/base/x/selection_utils.h"
#include "ui/base/x/x11_os_exchange_data_provider.h"
#include "ui/gfx/x/atom_cache.h"
#include "ui/gfx/x/connection.h"
#include "ui/ozone/public/ozone_platform.h"
#endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE_X11)
namespace content {
namespace {
using ::ui::mojom::DragOperation;
constexpr gfx::Rect kBounds = gfx::Rect(0, 0, 20, 20);
constexpr gfx::PointF kClientPt = {5, 10};
constexpr gfx::PointF kScreenPt = {17, 3};
// Runs a specified callback when a ui::MouseEvent is received.
class RunCallbackOnActivation : public WebContentsDelegate {
public:
explicit RunCallbackOnActivation(base::OnceClosure closure)
: closure_(std::move(closure)) {}
RunCallbackOnActivation(const RunCallbackOnActivation&) = delete;
RunCallbackOnActivation& operator=(const RunCallbackOnActivation&) = delete;
~RunCallbackOnActivation() override = default;
// WebContentsDelegate:
void ActivateContents(WebContents* contents) override {
std::move(closure_).Run();
}
private:
base::OnceClosure closure_;
};
class PrivilegedWebContentsDelegate : public WebContentsDelegate {
public:
// WebContentsDelegate:
bool IsPrivileged() override { return true; }
};
class TestDragDropClient : public aura::client::DragDropClient {
public:
// aura::client::DragDropClient:
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 {
drag_in_progress_ = true;
drag_drop_data_ = std::move(data);
return DragOperation::kCopy;
}
#if BUILDFLAG(IS_LINUX)
void UpdateDragImage(const gfx::ImageSkia& image,
const gfx::Vector2d& offset) override {}
#endif
void DragCancel() override { drag_in_progress_ = false; }
bool IsDragDropInProgress() override { return drag_in_progress_; }
void AddObserver(aura::client::DragDropClientObserver* observer) override {}
void RemoveObserver(aura::client::DragDropClientObserver* observer) override {
}
ui::OSExchangeData* GetDragDropData() { return drag_drop_data_.get(); }
private:
bool drag_in_progress_ = false;
std::unique_ptr<ui::OSExchangeData> drag_drop_data_;
};
} // namespace
class WebContentsViewAuraTest : public RenderViewHostTestHarness {
public:
WebContentsViewAuraTest(const WebContentsViewAuraTest&) = delete;
WebContentsViewAuraTest& operator=(const WebContentsViewAuraTest&) = delete;
void OnDropComplete(RenderWidgetHostImpl* target_rwh,
const DropData& drop_data,
const gfx::PointF& client_pt,
const gfx::PointF& screen_pt,
int key_modifiers,
bool drop_allowed) {
// Cache the data for verification.
drop_complete_data_ = std::make_unique<DropCompleteData>(
target_rwh, drop_data, client_pt, screen_pt, key_modifiers,
drop_allowed);
std::move(async_drop_closure_).Run();
}
protected:
WebContentsViewAuraTest() = default;
~WebContentsViewAuraTest() override = default;
void SetUp() override {
RenderViewHostTestHarness::SetUp();
root_window()->SetBounds(kBounds);
GetNativeView()->SetBounds(kBounds);
GetNativeView()->Show();
root_window()->AddChild(GetNativeView());
occluding_window_.reset(aura::test::CreateTestWindowWithDelegateAndType(
nullptr, aura::client::WINDOW_TYPE_NORMAL, 0, kBounds, root_window(),
false));
}
void TearDown() override {
occluding_window_.reset();
RenderViewHostTestHarness::TearDown();
}
WebContentsViewAura* GetView() {
WebContentsImpl* contents = static_cast<WebContentsImpl*>(web_contents());
return static_cast<WebContentsViewAura*>(contents->GetView());
}
aura::Window* GetNativeView() { return web_contents()->GetNativeView(); }
void CheckDropData(WebContentsViewAura* view) const {
EXPECT_EQ(nullptr, view->current_drag_data_);
ASSERT_NE(nullptr, drop_complete_data_);
EXPECT_TRUE(drop_complete_data_->drop_allowed);
EXPECT_EQ(view->current_rwh_for_drag_.get(),
drop_complete_data_->target_rwh.get());
EXPECT_EQ(kClientPt, drop_complete_data_->client_pt);
// Screen point of event is ignored, instead cursor position used.
EXPECT_EQ(gfx::PointF(), drop_complete_data_->screen_pt);
EXPECT_EQ(0, drop_complete_data_->key_modifiers);
}
// |occluding_window_| occludes |web_contents()| when it's shown.
std::unique_ptr<aura::Window> occluding_window_;
// A closure indicating that async drop operation has completed.
base::OnceClosure async_drop_closure_;
struct DropCompleteData {
DropCompleteData(RenderWidgetHostImpl* target_rwh,
const DropData& drop_data,
const gfx::PointF& client_pt,
const gfx::PointF& screen_pt,
int key_modifiers,
bool drop_allowed)
: target_rwh(target_rwh->GetWeakPtr()),
drop_data(drop_data),
client_pt(client_pt),
screen_pt(screen_pt),
key_modifiers(key_modifiers),
drop_allowed(drop_allowed) {}
base::WeakPtr<RenderWidgetHostImpl> target_rwh;
const DropData drop_data;
const gfx::PointF client_pt;
const gfx::PointF screen_pt;
const int key_modifiers;
const bool drop_allowed;
};
std::unique_ptr<DropCompleteData> drop_complete_data_;
};
TEST_F(WebContentsViewAuraTest, EnableDisableOverscroll) {
WebContentsViewAura* view = GetView();
view->SetOverscrollControllerEnabled(false);
EXPECT_FALSE(view->gesture_nav_simple_);
view->SetOverscrollControllerEnabled(true);
EXPECT_TRUE(view->gesture_nav_simple_);
}
TEST_F(WebContentsViewAuraTest, ShowHideParent) {
EXPECT_EQ(web_contents()->GetVisibility(), content::Visibility::VISIBLE);
root_window()->Hide();
EXPECT_EQ(web_contents()->GetVisibility(), content::Visibility::HIDDEN);
root_window()->Show();
EXPECT_EQ(web_contents()->GetVisibility(), content::Visibility::VISIBLE);
}
TEST_F(WebContentsViewAuraTest, WebContentsDestroyedDuringClick) {
RunCallbackOnActivation delegate(base::BindOnce(
&RenderViewHostTestHarness::DeleteContents, base::Unretained(this)));
web_contents()->SetDelegate(&delegate);
// Simulates the mouse press.
ui::MouseEvent mouse_event(ui::EventType::kMousePressed, gfx::Point(),
gfx::Point(), ui::EventTimeForNow(),
ui::EF_LEFT_MOUSE_BUTTON, 0);
ui::EventHandler* event_handler = GetView();
event_handler->OnMouseEvent(&mouse_event);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// The web-content is not activated during mouse-press on Linux.
// See comment in WebContentsViewAura::OnMouseEvent() for more details.
EXPECT_NE(web_contents(), nullptr);
#endif
}
TEST_F(WebContentsViewAuraTest, OccludeView) {
EXPECT_EQ(web_contents()->GetVisibility(), Visibility::VISIBLE);
occluding_window_->Show();
EXPECT_EQ(web_contents()->GetVisibility(), Visibility::OCCLUDED);
occluding_window_->Hide();
EXPECT_EQ(web_contents()->GetVisibility(), Visibility::VISIBLE);
}
// TODO(crbug.com/40190725): Enable these tests on Fuchsia when
// OSExchangeDataProviderFactory::CreateProvider is implemented.
#if BUILDFLAG(IS_FUCHSIA)
#define MAYBE_DragDropFiles DISABLED_DragDropFiles
#define MAYBE_DragDropFilesOriginateFromRenderer \
DISABLED_DragDropFilesOriginateFromRenderer
#define MAYBE_DragDropImageFromRenderer DISABLED_DragDropImageFromRenderer
#else
#define MAYBE_DragDropFiles DragDropFiles
#define MAYBE_DragDropFilesOriginateFromRenderer \
DragDropFilesOriginateFromRenderer
#define MAYBE_DragDropImageFromRenderer DragDropImageFromRenderer
#endif
TEST_F(WebContentsViewAuraTest, MAYBE_DragDropFiles) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
const std::u16string string_data = u"Some string data";
data->SetString(string_data);
#if BUILDFLAG(IS_WIN)
const std::vector<ui::FileInfo> test_file_infos = {
{base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file1")),
base::FilePath()},
{base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file2")),
base::FilePath()},
{
base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file3")),
base::FilePath(),
},
};
#else
const std::vector<ui::FileInfo> test_file_infos = {
{base::FilePath(FILE_PATH_LITERAL("/tmp/test_file1")), base::FilePath()},
{base::FilePath(FILE_PATH_LITERAL("/tmp/test_file2")), base::FilePath()},
{base::FilePath(FILE_PATH_LITERAL("/tmp/test_file3")), base::FilePath()},
};
#endif
data->SetFilenames(test_file_infos);
data->SetFileContents(base::FilePath(FILE_PATH_LITERAL("ignored")),
"ignored");
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// By design, Linux implementations return an empty string if file data
// is also present.
EXPECT_TRUE(!view->current_drag_data_->text ||
view->current_drag_data_->text->empty());
#else
EXPECT_EQ(string_data, view->current_drag_data_->text);
#endif
// FileContents should be ignored when Filenames exists
// (https://crbug.com/1251482).
EXPECT_FALSE(view->current_drag_data_->file_contents_source_url.is_valid());
EXPECT_TRUE(view->current_drag_data_->file_contents.empty());
std::vector<ui::FileInfo> retrieved_file_infos =
view->current_drag_data_->filenames;
ASSERT_EQ(test_file_infos.size(), retrieved_file_infos.size());
for (size_t i = 0; i < retrieved_file_infos.size(); i++) {
EXPECT_EQ(test_file_infos[i].path, retrieved_file_infos[i].path);
EXPECT_EQ(test_file_infos[i].display_name,
retrieved_file_infos[i].display_name);
}
// Simulate drop.
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
base::RunLoop run_loop;
async_drop_closure_ = run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(data), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
run_loop.Run();
CheckDropData(view);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// By design, Linux implementations returns an empty string if file data
// is also present.
EXPECT_TRUE(!drop_complete_data_->drop_data.text ||
drop_complete_data_->drop_data.text->empty());
#else
EXPECT_EQ(string_data, drop_complete_data_->drop_data.text);
#endif
retrieved_file_infos = drop_complete_data_->drop_data.filenames;
ASSERT_EQ(test_file_infos.size(), retrieved_file_infos.size());
for (size_t i = 0; i < retrieved_file_infos.size(); i++) {
EXPECT_EQ(test_file_infos[i].path, retrieved_file_infos[i].path);
EXPECT_EQ(test_file_infos[i].display_name,
retrieved_file_infos[i].display_name);
}
}
TEST_F(WebContentsViewAuraTest, MAYBE_DragDropFilesOriginateFromRenderer) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
const std::u16string string_data = u"Some string data";
data->SetString(string_data);
#if BUILDFLAG(IS_WIN)
const std::vector<ui::FileInfo> test_file_infos = {
{base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file1")),
base::FilePath()},
{base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file2")),
base::FilePath()},
{
base::FilePath(FILE_PATH_LITERAL("C:\\tmp\\test_file3")),
base::FilePath(),
},
};
#else
const std::vector<ui::FileInfo> test_file_infos = {
{base::FilePath(FILE_PATH_LITERAL("/tmp/test_file1")), base::FilePath()},
{base::FilePath(FILE_PATH_LITERAL("/tmp/test_file2")), base::FilePath()},
{base::FilePath(FILE_PATH_LITERAL("/tmp/test_file3")), base::FilePath()},
};
#endif
data->SetFilenames(test_file_infos);
// Simulate the drag originating in the renderer process, in which case
// any file data should be filtered out (anchor drag scenario) except in
// CHROMEOS.
data->MarkRendererTaintedFromOrigin(url::Origin());
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// By design, Linux implementations return an empty string if file data
// is also present.
EXPECT_TRUE(!view->current_drag_data_->text ||
view->current_drag_data_->text->empty());
#else
EXPECT_EQ(string_data, view->current_drag_data_->text);
#endif
#if BUILDFLAG(IS_CHROMEOS)
ASSERT_FALSE(view->current_drag_data_->filenames.empty());
#else
ASSERT_TRUE(view->current_drag_data_->filenames.empty());
#endif
// Simulate drop.
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
base::RunLoop run_loop;
async_drop_closure_ = run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(data), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
run_loop.Run();
CheckDropData(view);
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
// By design, Linux implementations returns an empty string if file data is
// also present.
EXPECT_TRUE(!drop_complete_data_->drop_data.text ||
drop_complete_data_->drop_data.text->empty());
#else
EXPECT_EQ(string_data, drop_complete_data_->drop_data.text);
#endif
#if BUILDFLAG(IS_CHROMEOS)
// CHROMEOS never filters out files from a drop, even if the drag
// originated from a renderer, because otherwise, it breaks the Files app.
ASSERT_FALSE(drop_complete_data_->drop_data.filenames.empty());
#else
ASSERT_TRUE(drop_complete_data_->drop_data.filenames.empty());
#endif
}
TEST_F(WebContentsViewAuraTest, MAYBE_DragDropImageFromRenderer) {
WebContentsViewAura* view = GetView();
const base::FilePath filename(FILE_PATH_LITERAL("image.jpg"));
const GURL source_url("file:///image.jpg");
const std::string file_contents = "contents";
const std::string url_spec = "http://example.com/image.jpg";
const GURL url(url_spec);
const std::u16string url_title = u"";
const std::u16string html = u"<img src='http://example.com/image.jpg'>";
auto data = std::make_unique<ui::OSExchangeData>();
#if BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE_X11)
// FileContents drag-drop in X relies on XDragDropClient::InitDrag() setting
// window property 'XdndDirectSave0' to filename. Since XDragDropClient is not
// created in this unittest, we will set this property manually to allow
// XOSExchangeDataProvider::GetFileContents() to succeed.
if (ui::OzonePlatform::GetPlatformNameForTest() == "x11") {
auto* connection = x11::Connection::Get();
x11::Window xwindow = connection->CreateDummyWindow("Test Window");
connection->SetStringProperty(xwindow, x11::GetAtom("XdndDirectSave0"),
x11::GetAtom("text/plain"), "image.jpg");
data = std::make_unique<ui::OSExchangeData>(
std::make_unique<ui::XOSExchangeDataProvider>(
xwindow, xwindow, ui::SelectionFormatMap()));
}
#endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(IS_OZONE_X11)
// As per WebContentsViewAura::PrepareDragData(), we must call
// SetFileContents() before SetURL() to get the expected contents since
// SetURL() creates a synthesized <filename>.url shortcut.
data->SetFileContents(filename, file_contents);
data->SetURL(url, url_title);
data->SetHtml(html, GURL());
data->MarkRendererTaintedFromOrigin(url::Origin());
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
EXPECT_EQ(base::ASCIIToUTF16(url_spec), *view->current_drag_data_->text);
EXPECT_EQ(url_spec, view->current_drag_data_->url);
EXPECT_EQ(url_title, view->current_drag_data_->url_title);
EXPECT_TRUE(view->current_drag_data_->filenames.empty());
EXPECT_EQ(file_contents, view->current_drag_data_->file_contents);
EXPECT_TRUE(view->current_drag_data_->file_contents_image_accessible);
EXPECT_EQ(source_url, view->current_drag_data_->file_contents_source_url);
EXPECT_EQ(FILE_PATH_LITERAL("jpg"),
view->current_drag_data_->file_contents_filename_extension);
EXPECT_EQ("", view->current_drag_data_->file_contents_content_disposition);
// Simulate drop.
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
base::RunLoop run_loop;
async_drop_closure_ = run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(data), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
run_loop.Run();
CheckDropData(view);
EXPECT_EQ(base::ASCIIToUTF16(url_spec), drop_complete_data_->drop_data.text);
EXPECT_EQ(url_spec, drop_complete_data_->drop_data.url);
EXPECT_EQ(url_title, drop_complete_data_->drop_data.url_title);
EXPECT_TRUE(drop_complete_data_->drop_data.filenames.empty());
EXPECT_EQ(file_contents, drop_complete_data_->drop_data.file_contents);
EXPECT_TRUE(drop_complete_data_->drop_data.file_contents_image_accessible);
EXPECT_EQ(source_url,
drop_complete_data_->drop_data.file_contents_source_url);
EXPECT_EQ(FILE_PATH_LITERAL("jpg"),
drop_complete_data_->drop_data.file_contents_filename_extension);
EXPECT_EQ("",
drop_complete_data_->drop_data.file_contents_content_disposition);
}
#if BUILDFLAG(IS_WIN)
TEST_F(WebContentsViewAuraTest, DragDropVirtualFiles) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
const std::u16string string_data = u"Some string data";
data->SetString(string_data);
const std::vector<std::pair<base::FilePath, std::string>>
test_filenames_and_contents = {
{base::FilePath(FILE_PATH_LITERAL("filename.txt")),
std::string("just some data")},
{base::FilePath(FILE_PATH_LITERAL("another filename.txt")),
std::string("just some data\0with\0nulls", 25)},
{base::FilePath(FILE_PATH_LITERAL("and another filename.txt")),
std::string("just some more data")},
};
data->provider().SetVirtualFileContentsForTesting(test_filenames_and_contents,
TYMED_ISTREAM);
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
EXPECT_EQ(string_data, view->current_drag_data_->text);
const base::FilePath path_placeholder(FILE_PATH_LITERAL("temp.tmp"));
std::vector<ui::FileInfo> retrieved_file_infos =
view->current_drag_data_->filenames;
ASSERT_EQ(test_filenames_and_contents.size(), retrieved_file_infos.size());
for (size_t i = 0; i < retrieved_file_infos.size(); i++) {
EXPECT_EQ(test_filenames_and_contents[i].first,
retrieved_file_infos[i].display_name);
EXPECT_EQ(path_placeholder, retrieved_file_infos[i].path);
}
// Simulate drop (completes asynchronously since virtual file data is
// present).
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
base::RunLoop run_loop;
async_drop_closure_ = run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(data), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
run_loop.Run();
CheckDropData(view);
EXPECT_EQ(string_data, drop_complete_data_->drop_data.text);
std::string read_contents;
base::FilePath temp_dir;
EXPECT_TRUE(base::GetTempDir(&temp_dir));
retrieved_file_infos = drop_complete_data_->drop_data.filenames;
ASSERT_EQ(test_filenames_and_contents.size(), retrieved_file_infos.size());
for (size_t i = 0; i < retrieved_file_infos.size(); i++) {
EXPECT_EQ(test_filenames_and_contents[i].first,
retrieved_file_infos[i].display_name);
// Check if the temp files that back the virtual files are actually created
// in the temp directory. Need to compare long file paths here because
// GetTempDir can return a short ("8.3") path if the test is run
// under a username that is too long.
EXPECT_EQ(base::MakeLongFilePath(temp_dir),
base::MakeLongFilePath(retrieved_file_infos[i].path.DirName()));
EXPECT_EQ(test_filenames_and_contents[i].first.Extension(),
retrieved_file_infos[i].path.Extension());
EXPECT_TRUE(
base::ReadFileToString(retrieved_file_infos[i].path, &read_contents));
EXPECT_EQ(test_filenames_and_contents[i].second, read_contents);
}
}
TEST_F(WebContentsViewAuraTest, DragDropVirtualFilesOriginateFromRenderer) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
const std::u16string string_data = u"Some string data";
data->SetString(string_data);
const std::vector<std::pair<base::FilePath, std::string>>
test_filenames_and_contents = {
{base::FilePath(FILE_PATH_LITERAL("filename.txt")),
std::string("just some data")},
{base::FilePath(FILE_PATH_LITERAL("another filename.txt")),
std::string("just some data\0with\0nulls", 25)},
{base::FilePath(FILE_PATH_LITERAL("and another filename.txt")),
std::string("just some more data")},
};
data->provider().SetVirtualFileContentsForTesting(test_filenames_and_contents,
TYMED_ISTREAM);
// Simulate the drag originating in the renderer process, in which case
// any file data should be filtered out (anchor drag scenario).
data->MarkRendererTaintedFromOrigin(url::Origin());
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
EXPECT_EQ(string_data, view->current_drag_data_->text);
ASSERT_TRUE(view->current_drag_data_->filenames.empty());
// Simulate drop (completes asynchronously since virtual file data is
// present).
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
base::RunLoop run_loop;
async_drop_closure_ = run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(data), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
run_loop.Run();
CheckDropData(view);
EXPECT_EQ(string_data, drop_complete_data_->drop_data.text);
ASSERT_TRUE(drop_complete_data_->drop_data.filenames.empty());
}
TEST_F(WebContentsViewAuraTest, DragDropVirtualFileGetsNonEmptyContents) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
const std::u16string string_data = u"Some string data";
data->SetString(string_data);
const base::FilePath test_filename(FILE_PATH_LITERAL("filename.txt"));
const std::string test_file_content = "just some data";
data->provider().SetVirtualFileContentsForTesting(
{{test_filename, test_file_content}}, TYMED_ISTREAM);
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
// Verify drag data is set with file contents
EXPECT_GT(view->current_drag_data_->file_contents.size(),
static_cast<size_t>(0));
EXPECT_EQ(test_file_content, view->current_drag_data_->file_contents);
}
TEST_F(WebContentsViewAuraTest, DragDropUrlData) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
data->MarkRendererTaintedFromOrigin(url::Origin());
const std::string url_spec = "https://www.wikipedia.org/";
const GURL url(url_spec);
const std::u16string url_title = u"Wikipedia";
data->SetURL(url, url_title);
// SetUrl should also add a virtual .url (internet shortcut) file.
std::optional<std::vector<ui::FileInfo>> file_infos =
data->GetVirtualFilenames();
ASSERT_TRUE(file_infos.has_value());
ASSERT_EQ(1ULL, file_infos.value().size());
EXPECT_EQ(base::FilePath(base::UTF16ToWide(url_title) + L".url"),
file_infos.value()[0].display_name);
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
EXPECT_EQ(url_spec, view->current_drag_data_->url);
EXPECT_EQ(url_title, view->current_drag_data_->url_title);
// Virtual files should not have been retrieved if url data present.
EXPECT_TRUE(view->current_drag_data_->filenames.empty());
// Shortcut *.url file contents created by SetURL() should be ignored
// (https://crbug.com/1274395).
EXPECT_TRUE(view->current_drag_data_->file_contents_source_url.is_empty());
EXPECT_TRUE(view->current_drag_data_->file_contents.empty());
// Simulate drop (completes asynchronously since virtual file data is
// present).
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
base::RunLoop run_loop;
async_drop_closure_ = run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
std::move(drop_cb).Run(std::move(data), output_drag_op,
/*drag_image_layer_owner=*/nullptr);
run_loop.Run();
CheckDropData(view);
EXPECT_EQ(url_spec, drop_complete_data_->drop_data.url);
EXPECT_EQ(url_title, drop_complete_data_->drop_data.url_title);
// Virtual files should not have been retrieved if url data present.
EXPECT_TRUE(drop_complete_data_->drop_data.filenames.empty());
EXPECT_TRUE(
drop_complete_data_->drop_data.file_contents_source_url.is_empty());
EXPECT_TRUE(drop_complete_data_->drop_data.file_contents.empty());
}
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_CHROMEOS)
TEST_F(WebContentsViewAuraTest, StartDragging) {
const char kGmailUrl[] = "http://mail.google.com/";
NavigateAndCommit(GURL(kGmailUrl));
FocusWebContentsOnMainFrame();
TestDragDropClient drag_drop_client;
aura::client::SetDragDropClient(root_window(), &drag_drop_client);
WebContentsViewAura* view = GetView();
// This condition is needed to avoid calling WebContentsViewAura::EndDrag
// which will result NOTREACHED being called in
// `RenderWidgetHostViewBase::TransformPointToCoordSpaceForView`.
view->drag_in_progress_ = true;
DropData drop_data;
drop_data.text.emplace(u"Hello World!");
view->StartDragging(drop_data, url::Origin(),
blink::DragOperationsMask::kDragOperationNone,
gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
blink::mojom::DragEventSourceInfo(),
RenderWidgetHostImpl::From(rvh()->GetWidget()));
ui::OSExchangeData* exchange_data = drag_drop_client.GetDragDropData();
EXPECT_TRUE(exchange_data);
EXPECT_TRUE(exchange_data->GetSource());
EXPECT_TRUE(exchange_data->GetSource()->IsUrlType());
EXPECT_EQ(*(exchange_data->GetSource()->GetURL()), GURL(kGmailUrl));
}
#endif // BUILDFLAG(IS_CHROMEOS)
TEST_F(WebContentsViewAuraTest,
RejectDragFromPrivilegedWebContentsToNonPrivilegedWebContents) {
WebContentsViewAura* view = GetView();
auto data = std::make_unique<ui::OSExchangeData>();
data->MarkAsFromPrivileged();
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_MOVE);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_EQ(nullptr, view->current_drag_data_);
}
TEST_F(WebContentsViewAuraTest,
AcceptDragFromPrivilegedWebContentsToPrivilegedWebContents) {
WebContentsViewAura* view = GetView();
PrivilegedWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
auto data = std::make_unique<ui::OSExchangeData>();
data->MarkAsFromPrivileged();
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_MOVE);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_NE(nullptr, view->current_drag_data_);
}
TEST_F(WebContentsViewAuraTest,
RejectDragFromNonPrivilegedWebContentsToPrivilegedWebContents) {
WebContentsViewAura* view = GetView();
PrivilegedWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
auto data = std::make_unique<ui::OSExchangeData>();
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_MOVE);
// Simulate drag enter.
EXPECT_EQ(nullptr, view->current_drag_data_);
view->OnDragEntered(event);
ASSERT_EQ(nullptr, view->current_drag_data_);
}
TEST_F(WebContentsViewAuraTest, StartDragFromPrivilegedWebContents) {
const char kGoogleUrl[] = "https://google.com/";
NavigateAndCommit(GURL(kGoogleUrl));
TestDragDropClient drag_drop_client;
aura::client::SetDragDropClient(root_window(), &drag_drop_client);
// Mark the Web Contents as native UI.
WebContentsViewAura* view = GetView();
PrivilegedWebContentsDelegate delegate;
web_contents()->SetDelegate(&delegate);
// This condition is needed to avoid calling WebContentsViewAura::EndDrag
// which will result NOTREACHED being called in
// `RenderWidgetHostViewBase::TransformPointToCoordSpaceForView`.
view->drag_in_progress_ = true;
DropData drop_data;
view->StartDragging(drop_data, url::Origin::Create(GURL(kGoogleUrl)),
blink::DragOperationsMask::kDragOperationNone,
gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
blink::mojom::DragEventSourceInfo(),
RenderWidgetHostImpl::From(rvh()->GetWidget()));
ui::OSExchangeData* exchange_data = drag_drop_client.GetDragDropData();
EXPECT_TRUE(exchange_data);
EXPECT_TRUE(exchange_data->IsFromPrivileged());
}
TEST_F(WebContentsViewAuraTest, EmptyTextInDropDataIsNonNullInOSExchangeData) {
const char kGoogleUrl[] = "https://google.com/";
// Declare an empty but NON-NULL string
std::u16string empty_string;
NavigateAndCommit(GURL(kGoogleUrl));
TestDragDropClient drag_drop_client;
aura::client::SetDragDropClient(root_window(), &drag_drop_client);
// Mark the Web Contents as native UI.
WebContentsViewAura* view = GetView();
// This condition is needed to avoid calling WebContentsViewAura::EndDrag
// which will result NOTREACHED being called in
// `RenderWidgetHostViewBase::TransformPointToCoordSpaceForView`.
view->drag_in_progress_ = true;
DropData drop_data;
drop_data.text = empty_string;
view->StartDragging(drop_data, url::Origin::Create(GURL(kGoogleUrl)),
blink::DragOperationsMask::kDragOperationNone,
gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
blink::mojom::DragEventSourceInfo(),
RenderWidgetHostImpl::From(rvh()->GetWidget()));
ui::OSExchangeData* exchange_data = drag_drop_client.GetDragDropData();
EXPECT_TRUE(exchange_data);
EXPECT_EQ(exchange_data->GetString(), empty_string);
}
TEST_F(WebContentsViewAuraTest,
EmptyTextWithUrlInDropDataIsEmptyInOSExchangeDataGetString) {
const char kGoogleUrl[] = "https://google.com/";
// Declare an empty but NON-NULL string
std::u16string empty_string;
NavigateAndCommit(GURL(kGoogleUrl));
TestDragDropClient drag_drop_client;
aura::client::SetDragDropClient(root_window(), &drag_drop_client);
// Mark the Web Contents as native UI.
WebContentsViewAura* view = GetView();
// This condition is needed to avoid calling WebContentsViewAura::EndDrag
// which will result NOTREACHED being called in
// `RenderWidgetHostViewBase::TransformPointToCoordSpaceForView`.
view->drag_in_progress_ = true;
DropData drop_data;
drop_data.text = empty_string;
drop_data.url = GURL(kGoogleUrl);
view->StartDragging(drop_data, url::Origin::Create(GURL(kGoogleUrl)),
blink::DragOperationsMask::kDragOperationNone,
gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
blink::mojom::DragEventSourceInfo(),
RenderWidgetHostImpl::From(rvh()->GetWidget()));
ui::OSExchangeData* exchange_data = drag_drop_client.GetDragDropData();
EXPECT_TRUE(exchange_data);
EXPECT_EQ(exchange_data->GetString(), empty_string);
}
TEST_F(WebContentsViewAuraTest,
UrlInDropDataReturnsUrlInOSExchangeDataGetString) {
const char kGoogleUrl[] = "https://google.com/";
std::u16string url_string = u"https://google.com/";
NavigateAndCommit(GURL(kGoogleUrl));
TestDragDropClient drag_drop_client;
aura::client::SetDragDropClient(root_window(), &drag_drop_client);
// Mark the Web Contents as native UI.
WebContentsViewAura* view = GetView();
// This condition is needed to avoid calling WebContentsViewAura::EndDrag
// which will result NOTREACHED being called in
// `RenderWidgetHostViewBase::TransformPointToCoordSpaceForView`.
view->drag_in_progress_ = true;
DropData drop_data;
drop_data.url = GURL(kGoogleUrl);
view->StartDragging(drop_data, url::Origin::Create(GURL(kGoogleUrl)),
blink::DragOperationsMask::kDragOperationNone,
gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
blink::mojom::DragEventSourceInfo(),
RenderWidgetHostImpl::From(rvh()->GetWidget()));
ui::OSExchangeData* exchange_data = drag_drop_client.GetDragDropData();
EXPECT_TRUE(exchange_data);
EXPECT_EQ(exchange_data->GetString(), url_string);
}
TEST_F(WebContentsViewAuraTest, EndDragIsCalledAfterAsyncDrop) {
const char kGoogleUrl[] = "https://google.com/";
// Declare an empty but NON-NULL string
std::u16string empty_string;
NavigateAndCommit(GURL(kGoogleUrl));
DropData drop_data;
drop_data.text = empty_string;
drop_data.url = GURL(kGoogleUrl);
TestDragDropClient drag_drop_client;
aura::client::SetDragDropClient(root_window(), &drag_drop_client);
// Mark the Web Contents as native UI.
WebContentsViewAura* view = GetView();
// Make sure EndDrag() is called async.
view->drag_in_progress_ = true;
auto data = std::make_unique<ui::OSExchangeData>();
const std::u16string string_data = u"Some string data";
data->SetString(string_data);
ui::DropTargetEvent event(*data.get(), kClientPt, kScreenPt,
ui::DragDropTypes::DRAG_COPY);
view->StartDragging(drop_data, url::Origin::Create(GURL(kGoogleUrl)),
blink::DragOperationsMask::kDragOperationNone,
gfx::ImageSkia(), gfx::Vector2d(), gfx::Rect(),
blink::mojom::DragEventSourceInfo(),
RenderWidgetHostImpl::From(rvh()->GetWidget()));
// Simulate drop.
auto callback = base::BindOnce(&WebContentsViewAuraTest::OnDropComplete,
base::Unretained(this));
view->RegisterDropCallbackForTesting(std::move(callback));
// Simulate `EndDrag`.
base::RunLoop end_drag_run_loop;
view->end_drag_runner_.ReplaceClosure(end_drag_run_loop.QuitClosure());
base::RunLoop end_drop_run_loop;
async_drop_closure_ = end_drop_run_loop.QuitClosure();
auto drop_cb = view->GetDropCallback(event);
ASSERT_TRUE(drop_cb);
ui::mojom::DragOperation output_drag_op = ui::mojom::DragOperation::kNone;
// Post `drop_cb` to simulate an async drop processing. This happens
// when `PerformDropOrExitDrag` or `PerformDropCallback` is async.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(drop_cb), std::move(data),
std::ref(output_drag_op),
/*drag_image_layer_owner=*/nullptr));
end_drop_run_loop.Run();
CheckDropData(view);
end_drag_run_loop.Run();
}
} // namespace content