| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include <memory> | |
| 6 #include <string> | |
| 7 | |
| 8 #include "base/macros.h" | |
| 9 #include "base/strings/pattern.h" | |
| 10 #include "base/strings/string_piece.h" | |
| 11 #include "base/strings/stringprintf.h" | |
| 12 #include "base/strings/utf_string_conversions.h" | |
| 13 #include "content/browser/frame_host/frame_tree.h" | |
| 14 #include "content/browser/frame_host/frame_tree_node.h" | |
| 15 #include "content/browser/frame_host/render_frame_host_impl.h" | |
| 16 #include "content/browser/web_contents/web_contents_impl.h" | |
| 17 #include "content/public/browser/render_frame_host.h" | |
| 18 #include "content/public/browser/web_contents.h" | |
| 19 #include "content/public/test/browser_test_utils.h" | |
| 20 #include "content/public/test/content_browser_test.h" | |
| 21 #include "content/public/test/content_browser_test_utils.h" | |
| 22 #include "content/public/test/test_utils.h" | |
| 23 #include "content/shell/browser/shell.h" | |
| 24 #include "net/base/escape.h" | |
| 25 #include "net/dns/mock_host_resolver.h" | |
| 26 #include "net/test/embedded_test_server/embedded_test_server.h" | |
| 27 #include "testing/gmock/include/gmock/gmock.h" | |
| 28 #include "testing/gtest/include/gtest/gtest.h" | |
| 29 #include "ui/aura/client/drag_drop_delegate.h" | |
| 30 #include "ui/aura/client/screen_position_client.h" | |
| 31 #include "ui/aura/window.h" | |
| 32 #include "ui/base/dragdrop/drag_drop_types.h" | |
| 33 #include "ui/base/dragdrop/drop_target_event.h" | |
| 34 #include "ui/base/dragdrop/os_exchange_data.h" | |
| 35 #include "ui/gfx/geometry/point.h" | |
| 36 #include "ui/gfx/geometry/rect.h" | |
| 37 #include "url/gurl.h" | |
| 38 | |
| 39 namespace content { | |
| 40 | |
| 41 namespace { | |
| 42 | |
| 43 // TODO(lukasza): Support testing on non-Aura platforms (i.e. Android + Mac?). | |
| 44 // | |
| 45 // Notes for the TODO above: | |
| 46 // | |
| 47 // - Why inject/simulate drag-and-drop events at the aura::Window* level. | |
| 48 // | |
| 49 // - It seems better to inject into UI libraries to cover code *inside* these | |
| 50 // libraries. This might complicate simulation a little bit (i.e. picking | |
| 51 // the right aura::Window and/or aura::client::DragDropDelegate to target), | |
| 52 // but otherwise important bits of code wouldn't get test coverage (i.e. | |
| 53 // directly injecting into RenderViewHost->DragTargetDragEnter seems wrong). | |
| 54 // | |
| 55 // - In theory, we could introduce WebContentsImpl::DragTargetDragEnter (to be | |
| 56 // used by all UI platforms - so reused by web_contents_view_android.cc, | |
| 57 // web_contents_view_aura.cc, web_drag_dest_mac.mm), but it feels wrong - UI | |
| 58 // libraries should already know which widget is the target of the event and | |
| 59 // so should be able to talk directly to the right widget (i.e. WebContents | |
| 60 // should not be responsible for mapping coordinates to a widget - this is | |
| 61 // the job of the UI library). | |
| 62 // | |
| 63 // - Unknowns: | |
| 64 // | |
| 65 // - Will this work for WebView and Plugin testing. | |
| 66 // | |
| 67 // - Will this work for simulating dragging from WebContents into outside of | |
| 68 // the browser (without leaving OS drag&drop machinery in a weird state). | |
| 69 | |
| 70 // Test helper for simulating drag and drop happening in WebContents. | |
| 71 class DragAndDropSimulator { | |
| 72 public: | |
| 73 explicit DragAndDropSimulator(WebContents* web_contents) | |
| 74 : web_contents_(web_contents) {} | |
| 75 | |
| 76 // Simulates notification that |text| was dragged from outside of the browser, | |
| 77 // into the specified |location| inside |web_contents|. | |
| 78 // |location| is relative to |web_contents|. | |
| 79 // Returns true upon success. | |
| 80 bool SimulateDragEnter(gfx::Point location, const std::string& text) { | |
| 81 ui::OSExchangeData data; | |
| 82 data.SetString(base::UTF8ToUTF16(text)); | |
| 83 return SimulateDragEnter(location, data); | |
| 84 } | |
| 85 | |
| 86 // Simulates dropping of the drag-and-dropped item. | |
| 87 // SimulateDragEnter needs to be called first. | |
| 88 // Returns true upon success. | |
| 89 bool SimulateDrop(gfx::Point location) { | |
| 90 if (!active_drag_event_) { | |
| 91 ADD_FAILURE() << "Cannot drop a drag that hasn't started yet."; | |
| 92 return false; | |
| 93 } | |
| 94 | |
| 95 aura::client::DragDropDelegate* delegate = GetDragDropDelegate(); | |
| 96 if (!delegate) | |
| 97 return false; | |
| 98 | |
| 99 gfx::Point event_location; | |
| 100 gfx::Point event_root_location; | |
| 101 CalculateEventLocations(location, &event_location, &event_root_location); | |
| 102 active_drag_event_->set_location(event_location); | |
| 103 active_drag_event_->set_root_location(event_root_location); | |
| 104 | |
| 105 delegate->OnDragUpdated(*active_drag_event_); | |
| 106 delegate->OnPerformDrop(*active_drag_event_); | |
| 107 return true; | |
| 108 } | |
| 109 | |
| 110 private: | |
| 111 bool SimulateDragEnter(gfx::Point location, const ui::OSExchangeData& data) { | |
| 112 if (active_drag_event_) { | |
| 113 ADD_FAILURE() << "Cannot start a new drag when old one hasn't ended yet."; | |
| 114 return false; | |
| 115 } | |
| 116 | |
| 117 aura::client::DragDropDelegate* delegate = GetDragDropDelegate(); | |
| 118 if (!delegate) | |
| 119 return false; | |
| 120 | |
| 121 gfx::Point event_location; | |
| 122 gfx::Point event_root_location; | |
| 123 CalculateEventLocations(location, &event_location, &event_root_location); | |
| 124 active_drag_event_.reset(new ui::DropTargetEvent( | |
| 125 data, event_location, event_root_location, kDefaultSourceOperations)); | |
| 126 | |
| 127 delegate->OnDragEntered(*active_drag_event_); | |
| 128 delegate->OnDragUpdated(*active_drag_event_); | |
| 129 return true; | |
| 130 } | |
| 131 | |
| 132 aura::client::DragDropDelegate* GetDragDropDelegate() { | |
| 133 gfx::NativeView view = web_contents_->GetContentNativeView(); | |
| 134 aura::client::DragDropDelegate* delegate = | |
| 135 aura::client::GetDragDropDelegate(view); | |
| 136 EXPECT_TRUE(delegate) << "Expecting WebContents to have DragDropDelegate"; | |
| 137 return delegate; | |
| 138 } | |
| 139 | |
| 140 void CalculateEventLocations(gfx::Point web_contents_relative_location, | |
| 141 gfx::Point* out_event_location, | |
| 142 gfx::Point* out_event_root_location) { | |
| 143 gfx::NativeView view = web_contents_->GetNativeView(); | |
| 144 | |
| 145 *out_event_location = web_contents_relative_location; | |
| 146 | |
| 147 gfx::Point root_location = web_contents_relative_location; | |
| 148 aura::Window::ConvertPointToTarget(view, view->GetRootWindow(), | |
| 149 &root_location); | |
| 150 *out_event_location = root_location; | |
| 151 } | |
| 152 | |
| 153 // These are ui::DropTargetEvent::source_operations_ being sent when manually | |
| 154 // trying out drag&drop of an image file from Nemo (Ubuntu's file explorer) | |
| 155 // into a content_shell. | |
| 156 static constexpr int kDefaultSourceOperations = ui::DragDropTypes::DRAG_MOVE | | |
| 157 ui::DragDropTypes::DRAG_COPY | | |
| 158 ui::DragDropTypes::DRAG_LINK; | |
| 159 | |
| 160 WebContents* web_contents_; | |
| 161 std::unique_ptr<ui::DropTargetEvent> active_drag_event_; | |
| 162 | |
| 163 DISALLOW_COPY_AND_ASSIGN(DragAndDropSimulator); | |
| 164 }; | |
| 165 | |
| 166 // Helper for waiting for notifications from | |
| 167 // content/test/data/drag_and_drop/event_monitoring.js | |
| 168 class DOMDragEventWaiter { | |
| 169 public: | |
| 170 explicit DOMDragEventWaiter(const std::string& event_type_to_wait_for, | |
| 171 const ToRenderFrameHost& target) | |
| 172 : target_frame_name_(target.render_frame_host()->GetFrameName()), | |
| 173 event_type_to_wait_for_(event_type_to_wait_for), | |
| 174 dom_message_queue_( | |
| 175 WebContents::FromRenderFrameHost(target.render_frame_host())) {} | |
| 176 | |
| 177 // Waits until |target| calls reportDragEvent in | |
| 178 // content/test/data/drag_and_drop/event_monitoring.js with event_type | |
| 179 // property set to |event_type_to_wait_for|. (|target| and | |
| 180 // |event_type_to_wait_for| are passed to the constructor). | |
| 181 // | |
| 182 // Returns the event details via |found_event| (in form of a JSON-encoded | |
| 183 // object). See content/test/data/drag_and_drop/event_monitoring.js for keys | |
| 184 // and properties that |found_event| is expected to have. | |
| 185 // | |
| 186 // Returns true upon success. It is okay if |response| is null. | |
| 187 bool WaitForNextMatchingEvent(std::string* found_event) WARN_UNUSED_RESULT { | |
| 188 std::string candidate_event; | |
| 189 bool got_right_event_type = false; | |
| 190 bool got_right_window_name = false; | |
| 191 do { | |
| 192 if (!dom_message_queue_.WaitForMessage(&candidate_event)) | |
| 193 return false; | |
| 194 | |
| 195 got_right_event_type = base::MatchPattern( | |
| 196 candidate_event, base::StringPrintf("*\"event_type\":\"%s\"*", | |
| 197 event_type_to_wait_for_.c_str())); | |
| 198 | |
| 199 got_right_window_name = base::MatchPattern( | |
| 200 candidate_event, base::StringPrintf("*\"window_name\":\"%s\"*", | |
| 201 target_frame_name_.c_str())); | |
| 202 } while (!got_right_event_type || !got_right_window_name); | |
| 203 | |
| 204 if (found_event) | |
| 205 *found_event = candidate_event; | |
| 206 | |
| 207 return true; | |
| 208 } | |
| 209 | |
| 210 private: | |
| 211 std::string target_frame_name_; | |
| 212 std::string event_type_to_wait_for_; | |
| 213 DOMMessageQueue dom_message_queue_; | |
| 214 }; | |
| 215 | |
| 216 const char kTestPagePath[] = "/drag_and_drop/page.html"; | |
| 217 | |
| 218 } // namespace | |
| 219 | |
| 220 class DragAndDropBrowserTest : public ContentBrowserTest { | |
| 221 public: | |
| 222 DragAndDropBrowserTest(){}; | |
| 223 | |
| 224 protected: | |
| 225 void SetUpOnMainThread() override { | |
| 226 host_resolver()->AddRule("*", "127.0.0.1"); | |
| 227 ASSERT_TRUE(embedded_test_server()->Start()); | |
| 228 content::SetupCrossSiteRedirector(embedded_test_server()); | |
| 229 drag_simulator_.reset(new DragAndDropSimulator(shell()->web_contents())); | |
| 230 } | |
| 231 | |
| 232 // Navigates to content/test/data/drag_and_drop/page.html, with page origin | |
| 233 // and frame contents being controlled by the parameters. | |
| 234 bool NavigateToTestPage(const std::string& main_origin, | |
| 235 const std::string& left_frame, | |
| 236 const std::string& right_frame) { | |
| 237 GURL base_url = embedded_test_server()->GetURL(main_origin, kTestPagePath); | |
| 238 | |
| 239 std::string left_arg; | |
| 240 if (!left_frame.empty()) { | |
| 241 left_arg = net::EscapeQueryParamValue( | |
| 242 std::string("/cross-site/") + left_frame, true); | |
| 243 } | |
| 244 std::string right_arg; | |
| 245 if (!right_frame.empty()) { | |
| 246 right_arg = net::EscapeQueryParamValue( | |
| 247 std::string("/cross-site/") + right_frame, true); | |
| 248 } | |
| 249 std::string query = base::StringPrintf("left=%s&right=%s", left_arg.c_str(), | |
| 250 right_arg.c_str()); | |
| 251 GURL::Replacements query_replacement; | |
| 252 query_replacement.SetQueryStr(query); | |
| 253 GURL target_url = base_url.ReplaceComponents(query_replacement); | |
| 254 | |
| 255 return NavigateToURL(shell(), target_url); | |
| 256 } | |
| 257 | |
| 258 bool SimulateDragEnterToRightFrame(const std::string& text) { | |
| 259 AssertTestPageIsLoaded(); | |
| 260 return drag_simulator_->SimulateDragEnter(kMiddleOfRightFrame, text); | |
| 261 } | |
| 262 | |
| 263 bool SimulateDropInRightFrame() { | |
| 264 AssertTestPageIsLoaded(); | |
| 265 return drag_simulator_->SimulateDrop(kMiddleOfRightFrame); | |
| 266 } | |
| 267 | |
| 268 RenderFrameHost* right_frame() { | |
| 269 AssertTestPageIsLoaded(); | |
| 270 return GetFrameByName("right"); | |
| 271 } | |
| 272 | |
| 273 private: | |
| 274 RenderFrameHost* GetFrameByName(const std::string& name_to_find) { | |
| 275 WebContentsImpl* web_contents = | |
| 276 static_cast<WebContentsImpl*>(shell()->web_contents()); | |
| 277 FrameTree* frame_tree = web_contents->GetFrameTree(); | |
| 278 return frame_tree->FindByName(name_to_find)->current_frame_host(); | |
| 279 } | |
| 280 | |
| 281 void AssertTestPageIsLoaded() { | |
| 282 ASSERT_EQ(kTestPagePath, | |
| 283 shell()->web_contents()->GetLastCommittedURL().path()); | |
| 284 } | |
| 285 | |
| 286 std::unique_ptr<DragAndDropSimulator> drag_simulator_; | |
| 287 | |
| 288 // Constants with coordinates within content/test/data/drag_and_drop/page.html | |
| 289 const gfx::Point kMiddleOfLeftFrame = gfx::Point(200, 200); | |
| 290 const gfx::Point kMiddleOfRightFrame = gfx::Point(400, 200); | |
| 291 | |
| 292 DISALLOW_COPY_AND_ASSIGN(DragAndDropBrowserTest); | |
| 293 }; | |
| 294 | |
| 295 IN_PROC_BROWSER_TEST_F(DragAndDropBrowserTest, DropTextFromOutside) { | |
| 296 ASSERT_TRUE(NavigateToTestPage("a.com", | |
| 297 "", // Left frame is unused by this test. | |
| 298 "c.com/drag_and_drop/drop_target.html")); | |
| 299 | |
| 300 { | |
| 301 std::string dragover_event; | |
| 302 DOMDragEventWaiter dragover_waiter("dragover", right_frame()); | |
| 303 ASSERT_TRUE(SimulateDragEnterToRightFrame("Dragged test text")); | |
| 304 ASSERT_TRUE(dragover_waiter.WaitForNextMatchingEvent(&dragover_event)); | |
| 305 EXPECT_THAT(dragover_event, testing::HasSubstr("\"drop_effect\":\"none\"")); | |
| 306 EXPECT_THAT(dragover_event, | |
| 307 testing::HasSubstr("\"effect_allowed\":\"all\"")); | |
| 308 EXPECT_THAT(dragover_event, | |
| 309 testing::HasSubstr("\"mime_types\":\"text/plain\"")); | |
| 310 } | |
| 311 | |
| 312 { | |
| 313 std::string drop_event; | |
| 314 DOMDragEventWaiter drop_waiter("drop", right_frame()); | |
| 315 ASSERT_TRUE(SimulateDropInRightFrame()); | |
| 316 ASSERT_TRUE(drop_waiter.WaitForNextMatchingEvent(&drop_event)); | |
| 317 EXPECT_THAT(drop_event, testing::HasSubstr("\"drop_effect\":\"none\"")); | |
| 318 EXPECT_THAT(drop_event, testing::HasSubstr("\"effect_allowed\":\"all\"")); | |
| 319 EXPECT_THAT(drop_event, | |
| 320 testing::HasSubstr("\"mime_types\":\"text/plain\"")); | |
| 321 } | |
| 322 } | |
| 323 | |
| 324 } // namespace chrome | |
| OLD | NEW |