Index: content/browser/web_contents/drag_and_drop_browsertest.cc |
diff --git a/content/browser/web_contents/drag_and_drop_browsertest.cc b/content/browser/web_contents/drag_and_drop_browsertest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..71dfc4765bd61ba0b812ee8707e666878c31d0e8 |
--- /dev/null |
+++ b/content/browser/web_contents/drag_and_drop_browsertest.cc |
@@ -0,0 +1,324 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <memory> |
+#include <string> |
+ |
+#include "base/macros.h" |
+#include "base/strings/pattern.h" |
+#include "base/strings/string_piece.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/strings/utf_string_conversions.h" |
+#include "content/browser/frame_host/frame_tree.h" |
+#include "content/browser/frame_host/frame_tree_node.h" |
+#include "content/browser/frame_host/render_frame_host_impl.h" |
+#include "content/browser/web_contents/web_contents_impl.h" |
+#include "content/public/browser/render_frame_host.h" |
+#include "content/public/browser/web_contents.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/test_utils.h" |
+#include "content/shell/browser/shell.h" |
+#include "net/base/escape.h" |
+#include "net/dns/mock_host_resolver.h" |
+#include "net/test/embedded_test_server/embedded_test_server.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.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/os_exchange_data.h" |
+#include "ui/gfx/geometry/point.h" |
+#include "ui/gfx/geometry/rect.h" |
+#include "url/gurl.h" |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+// 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. |
+// |
+// - Will this work for simulating dragging from WebContents into outside of |
+// the browser (without leaving OS drag&drop machinery in a weird state). |
+ |
+// Test helper for simulating drag and drop happening in WebContents. |
+class DragAndDropSimulator { |
+ public: |
+ explicit DragAndDropSimulator(WebContents* web_contents) |
+ : web_contents_(web_contents) {} |
+ |
+ // 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(gfx::Point location, const std::string& text) { |
+ ui::OSExchangeData data; |
+ data.SetString(base::UTF8ToUTF16(text)); |
+ return SimulateDragEnter(location, data); |
+ } |
+ |
+ // Simulates dropping of the drag-and-dropped item. |
+ // SimulateDragEnter needs to be called first. |
+ // Returns true upon success. |
+ bool SimulateDrop(gfx::Point location) { |
+ if (!active_drag_event_) { |
+ ADD_FAILURE() << "Cannot drop a drag that hasn't started yet."; |
+ return false; |
+ } |
+ |
+ aura::client::DragDropDelegate* delegate = GetDragDropDelegate(); |
+ if (!delegate) |
+ return false; |
+ |
+ gfx::Point event_location; |
+ gfx::Point event_root_location; |
+ CalculateEventLocations(location, &event_location, &event_root_location); |
+ active_drag_event_->set_location(event_location); |
+ active_drag_event_->set_root_location(event_root_location); |
+ |
+ delegate->OnDragUpdated(*active_drag_event_); |
+ delegate->OnPerformDrop(*active_drag_event_); |
+ return true; |
+ } |
+ |
+ private: |
+ bool SimulateDragEnter(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 = GetDragDropDelegate(); |
+ if (!delegate) |
+ return false; |
+ |
+ gfx::Point event_location; |
+ gfx::Point event_root_location; |
+ CalculateEventLocations(location, &event_location, &event_root_location); |
+ active_drag_event_.reset(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* GetDragDropDelegate() { |
+ gfx::NativeView view = web_contents_->GetContentNativeView(); |
+ aura::client::DragDropDelegate* delegate = |
+ aura::client::GetDragDropDelegate(view); |
+ EXPECT_TRUE(delegate) << "Expecting WebContents to have DragDropDelegate"; |
+ return delegate; |
+ } |
+ |
+ void CalculateEventLocations(gfx::Point web_contents_relative_location, |
+ gfx::Point* out_event_location, |
+ gfx::Point* out_event_root_location) { |
+ gfx::NativeView view = web_contents_->GetNativeView(); |
+ |
+ *out_event_location = web_contents_relative_location; |
+ |
+ gfx::Point root_location = web_contents_relative_location; |
+ aura::Window::ConvertPointToTarget(view, view->GetRootWindow(), |
+ &root_location); |
+ *out_event_location = 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* web_contents_; |
+ std::unique_ptr<ui::DropTargetEvent> active_drag_event_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DragAndDropSimulator); |
+}; |
+ |
+// Helper for waiting for notifications from |
+// content/test/data/drag_and_drop/event_monitoring.js |
+class DOMDragEventWaiter { |
+ public: |
+ explicit DOMDragEventWaiter(const std::string& event_type_to_wait_for, |
+ const ToRenderFrameHost& target) |
+ : target_frame_name_(target.render_frame_host()->GetFrameName()), |
+ event_type_to_wait_for_(event_type_to_wait_for), |
+ dom_message_queue_( |
+ WebContents::FromRenderFrameHost(target.render_frame_host())) {} |
+ |
+ // Waits until |target| calls reportDragEvent in |
+ // content/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 content/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. |
+ bool WaitForNextMatchingEvent(std::string* found_event) WARN_UNUSED_RESULT { |
+ 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 = base::MatchPattern( |
+ candidate_event, base::StringPrintf("*\"event_type\":\"%s\"*", |
+ event_type_to_wait_for_.c_str())); |
+ |
+ got_right_window_name = base::MatchPattern( |
+ candidate_event, base::StringPrintf("*\"window_name\":\"%s\"*", |
+ target_frame_name_.c_str())); |
+ } while (!got_right_event_type || !got_right_window_name); |
+ |
+ if (found_event) |
+ *found_event = candidate_event; |
+ |
+ return true; |
+ } |
+ |
+ private: |
+ std::string target_frame_name_; |
+ std::string event_type_to_wait_for_; |
+ DOMMessageQueue dom_message_queue_; |
+}; |
+ |
+const char kTestPagePath[] = "/drag_and_drop/page.html"; |
+ |
+} // namespace |
+ |
+class DragAndDropBrowserTest : public ContentBrowserTest { |
+ public: |
+ DragAndDropBrowserTest(){}; |
+ |
+ protected: |
+ void SetUpOnMainThread() override { |
+ host_resolver()->AddRule("*", "127.0.0.1"); |
+ ASSERT_TRUE(embedded_test_server()->Start()); |
+ content::SetupCrossSiteRedirector(embedded_test_server()); |
+ drag_simulator_.reset(new DragAndDropSimulator(shell()->web_contents())); |
+ } |
+ |
+ // Navigates to content/test/data/drag_and_drop/page.html, with page origin |
+ // and frame contents being controlled by the parameters. |
+ bool NavigateToTestPage(const std::string& main_origin, |
+ const std::string& left_frame, |
+ const std::string& right_frame) { |
+ GURL base_url = embedded_test_server()->GetURL(main_origin, kTestPagePath); |
+ |
+ std::string left_arg; |
+ if (!left_frame.empty()) { |
+ left_arg = net::EscapeQueryParamValue( |
+ std::string("/cross-site/") + left_frame, true); |
+ } |
+ std::string right_arg; |
+ if (!right_frame.empty()) { |
+ right_arg = net::EscapeQueryParamValue( |
+ std::string("/cross-site/") + right_frame, true); |
+ } |
+ std::string query = base::StringPrintf("left=%s&right=%s", left_arg.c_str(), |
+ right_arg.c_str()); |
+ GURL::Replacements query_replacement; |
+ query_replacement.SetQueryStr(query); |
+ GURL target_url = base_url.ReplaceComponents(query_replacement); |
+ |
+ return NavigateToURL(shell(), target_url); |
+ } |
+ |
+ bool SimulateDragEnterToRightFrame(const std::string& text) { |
+ AssertTestPageIsLoaded(); |
+ return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, text); |
+ } |
+ |
+ bool SimulateDropInRightFrame() { |
+ AssertTestPageIsLoaded(); |
+ return drag_simulator_->SimulateDrop(kMiddleOfRightFrame); |
+ } |
+ |
+ RenderFrameHost* right_frame() { |
+ AssertTestPageIsLoaded(); |
+ return GetFrameByName("right"); |
+ } |
+ |
+ private: |
+ RenderFrameHost* GetFrameByName(const std::string& name_to_find) { |
+ WebContentsImpl* web_contents = |
+ static_cast<WebContentsImpl*>(shell()->web_contents()); |
+ FrameTree* frame_tree = web_contents->GetFrameTree(); |
+ return frame_tree->FindByName(name_to_find)->current_frame_host(); |
+ } |
+ |
+ void AssertTestPageIsLoaded() { |
+ ASSERT_EQ(kTestPagePath, |
+ shell()->web_contents()->GetLastCommittedURL().path()); |
+ } |
+ |
+ std::unique_ptr<DragAndDropSimulator> drag_simulator_; |
+ |
+ // Constants with coordinates within content/test/data/drag_and_drop/page.html |
+ const gfx::Point kMiddleOfLeftFrame = gfx::Point(200, 200); |
+ const gfx::Point kMiddleOfRightFrame = gfx::Point(400, 200); |
+ |
+ DISALLOW_COPY_AND_ASSIGN(DragAndDropBrowserTest); |
+}; |
+ |
+IN_PROC_BROWSER_TEST_F(DragAndDropBrowserTest, DropTextFromOutside) { |
+ ASSERT_TRUE(NavigateToTestPage("a.com", |
+ "", // Left frame is unused by this test. |
+ "c.com/drag_and_drop/drop_target.html")); |
+ |
+ { |
+ std::string dragover_event; |
+ DOMDragEventWaiter dragover_waiter("dragover", right_frame()); |
+ ASSERT_TRUE(SimulateDragEnterToRightFrame("Dragged test text")); |
+ ASSERT_TRUE(dragover_waiter.WaitForNextMatchingEvent(&dragover_event)); |
+ EXPECT_THAT(dragover_event, testing::HasSubstr("\"drop_effect\":\"none\"")); |
+ EXPECT_THAT(dragover_event, |
+ testing::HasSubstr("\"effect_allowed\":\"all\"")); |
+ EXPECT_THAT(dragover_event, |
+ testing::HasSubstr("\"mime_types\":\"text/plain\"")); |
+ } |
+ |
+ { |
+ std::string drop_event; |
+ DOMDragEventWaiter drop_waiter("drop", right_frame()); |
+ ASSERT_TRUE(SimulateDropInRightFrame()); |
+ ASSERT_TRUE(drop_waiter.WaitForNextMatchingEvent(&drop_event)); |
+ EXPECT_THAT(drop_event, testing::HasSubstr("\"drop_effect\":\"none\"")); |
+ EXPECT_THAT(drop_event, testing::HasSubstr("\"effect_allowed\":\"all\"")); |
+ EXPECT_THAT(drop_event, |
+ testing::HasSubstr("\"mime_types\":\"text/plain\"")); |
+ } |
+} |
+ |
+} // namespace chrome |