Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1097)

Unified Diff: content/browser/web_contents/drag_and_drop_browsertest.cc

Issue 2453693003: Browser tests for OOPIF support for drag-n-drop. (Closed)
Patch Set: Giving up and going back to using notifications. Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | content/public/test/browser_test_utils.h » ('j') | content/public/test/browser_test_utils.h » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..4d6b7aaf3098b151104a06888620494fe40562e4
--- /dev/null
+++ b/content/browser/web_contents/drag_and_drop_browsertest.cc
@@ -0,0 +1,343 @@
+// 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/string_piece.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.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/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).
Łukasz Anforowicz 2016/11/01 23:43:41 We'll have to work through the items above, but I
+
+// Test helper for simulating drag and drop happening in WebContents.
+class DragAndDropSimulator {
+ public:
+ 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 DOMAutomationWaiter {
ncarter (slow) 2016/11/01 19:50:52 I think this would be cleaner if this used composi
+ public:
+ explicit DOMDragEventWaiter(const std::string& event_type_to_wait_for,
+ const ToRenderFrameHost& target)
+ : DOMAutomationWaiter(
+ WebContents::FromRenderFrameHost(target.render_frame_host())),
+ target_frame_name_(target.render_frame_host()->GetFrameName()),
+ event_type_to_wait_for_(event_type_to_wait_for) {}
+
+ // 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 |response|. See
+ // content/test/data/drag_and_drop/event_monitoring.js for keys / properties
+ // that |response| is expected to have.
+ //
+ // Returns true upon success. It is okay if |response| is null.
+ bool WaitAndGetResponse(std::unique_ptr<base::DictionaryValue>* response)
+ WARN_UNUSED_RESULT {
+ std::unique_ptr<base::Value> untyped_response;
+ if (!DOMAutomationWaiter::WaitAndGetResponse(&untyped_response))
+ return false;
+
+ if (response) {
+ *response = base::DictionaryValue::From(std::move(untyped_response));
+ if (!*response)
+ return false;
+ }
+
+ return true;
+ }
+
+ protected:
+ bool ShouldStopWaiting(const base::Value& value) override {
ncarter (slow) 2016/11/01 19:50:52 Couldn't you just do this, with no changes to DOMM
+ const base::DictionaryValue* dictionary;
+ if (!value.GetAsDictionary(&dictionary))
+ return false;
+
+ std::string event_type;
+ if (!dictionary->GetString("event_type", &event_type))
+ return false;
+
+ std::string window_name;
+ if (!dictionary->GetString("window_name", &window_name))
+ return false;
+
+ return event_type == event_type_to_wait_for_ &&
+ window_name == target_frame_name_;
+ }
+
+ private:
+ std::string target_frame_name_;
+ std::string event_type_to_wait_for_;
+};
+
+std::string GetStringOrEmpty(const base::DictionaryValue& dict,
+ base::StringPiece path) {
+ std::string result;
+ if (dict.GetString(path, &result))
+ return result;
+ return "";
+}
+
+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) {
+ RenderFrameHost* result = nullptr;
+ for (RenderFrameHost* rfh : shell()->web_contents()->GetAllFrames()) {
+ if (rfh->GetFrameName() == name_to_find) {
+ if (result) {
+ ADD_FAILURE() << "More than one frame named "
+ << "'" << name_to_find << "'";
+ return nullptr;
+ }
+ result = rfh;
+ }
+ }
+
+ EXPECT_TRUE(result) << "Couldn't find a frame named "
+ << "'" << name_to_find << "'";
+ return result;
+ }
+
+ 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::unique_ptr<base::DictionaryValue> dragover_data;
+ DOMDragEventWaiter dragover_waiter("dragover", right_frame());
+ ASSERT_TRUE(SimulateDragEnterToRightFrame("Dragged test text"));
+ ASSERT_TRUE(dragover_waiter.WaitAndGetResponse(&dragover_data));
+ EXPECT_EQ("none", GetStringOrEmpty(*dragover_data, "drop_effect"));
+ EXPECT_EQ("all", GetStringOrEmpty(*dragover_data, "effect_allowed"));
+ EXPECT_EQ("text/plain", GetStringOrEmpty(*dragover_data, "mime_types"));
+ }
+
+ {
+ std::unique_ptr<base::DictionaryValue> drop_data;
+ DOMDragEventWaiter drop_waiter("drop", right_frame());
+ ASSERT_TRUE(SimulateDropInRightFrame());
+ ASSERT_TRUE(drop_waiter.WaitAndGetResponse(&drop_data));
+ EXPECT_EQ("none", GetStringOrEmpty(*drop_data, "drop_effect"));
+ EXPECT_EQ("all", GetStringOrEmpty(*drop_data, "effect_allowed"));
+ EXPECT_EQ("text/plain", GetStringOrEmpty(*drop_data, "mime_types"));
+ }
+}
+
+} // namespace chrome
« no previous file with comments | « no previous file | content/public/test/browser_test_utils.h » ('j') | content/public/test/browser_test_utils.h » ('J')

Powered by Google App Engine
This is Rietveld 408576698