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

Unified Diff: chrome/test/base/interactive_test_utils_mac.mm

Issue 1747803003: MacViews: Implement Tab Dragging (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Removed click simulation, reimplemented CocoaWindowMoveLoop without relying on the WindowServer. Created 4 years, 7 months 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
Index: chrome/test/base/interactive_test_utils_mac.mm
diff --git a/chrome/test/base/interactive_test_utils_mac.mm b/chrome/test/base/interactive_test_utils_mac.mm
index 0d00f3a6614e3aacd4c9141ed971ae02590ea1d1..db190c76b9d2d5ab8b337573f31b3ffb04dad04e 100644
--- a/chrome/test/base/interactive_test_utils_mac.mm
+++ b/chrome/test/base/interactive_test_utils_mac.mm
@@ -7,9 +7,179 @@
#include <Carbon/Carbon.h>
#import <Cocoa/Cocoa.h>
-#include "base/message_loop/message_loop.h"
+#include "base/threading/simple_thread.h"
+#include "base/threading/thread_task_runner_handle.h"
#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/ui/views/tabs/window_finder.h"
#import "ui/base/test/windowed_nsnotification_observer.h"
+#include "ui/gfx/animation/tween.h"
+#include "ui/gfx/mac/coordinate_conversion.h"
+#include "ui/views/cocoa/bridged_native_widget.h"
+#include "ui/views/event_monitor.h"
+#include "ui/views/widget/native_widget_mac.h"
+
+namespace {
+
+bool WaitForEvent(bool (^block)(const base::Closure& quit_closure)) {
tapted 2016/05/23 07:29:28 I replaced everything except `ScopedCGEventsEnable
themblsha 2016/05/26 15:13:25 Wow, amazing. It even works when simulating a drag
+ base::RunLoop runner;
+ bool result = block(runner.QuitClosure());
+ runner.Run();
+ return result;
+}
+
+bool MouseMove(const gfx::Point& p,
+ const base::TimeDelta& delay = base::TimeDelta()) {
tapted 2016/05/23 07:29:27 |delay| argument isn't needed if MoveWithoutAck is
+ if (!delay.is_zero()) {
+ bool result =
+ ui_controls::SendMouseMoveNotifyWhenDone(p.x(), p.y(), base::Closure());
+ usleep(delay.InMicroseconds());
+ return result;
+ }
+
+ return WaitForEvent(^(const base::Closure& quit_closure) {
+ return ui_controls::SendMouseMoveNotifyWhenDone(p.x(), p.y(), quit_closure);
+ });
+}
+
+bool MouseDown() {
+ return WaitForEvent(^(const base::Closure& quit_closure) {
+ return ui_controls::SendMouseEventsNotifyWhenDone(
+ ui_controls::LEFT, ui_controls::DOWN, quit_closure);
+ });
+}
+
+bool MouseUp() {
+ return WaitForEvent(^(const base::Closure& quit_closure) {
+ return ui_controls::SendMouseEventsNotifyWhenDone(
+ ui_controls::LEFT, ui_controls::UP, quit_closure);
+ });
+}
+
+std::vector<ui_test_utils::DragAndDropOperation> DragAndDropMoveOperations(
tapted 2016/05/23 07:29:27 It looks like this is only ever needed to calculat
+ const std::list<ui_test_utils::DragAndDropOperation>& operations) {
+ std::vector<ui_test_utils::DragAndDropOperation> move_operations;
+ std::copy_if(operations.begin(), operations.end(),
+ std::back_inserter(move_operations),
+ [](const ui_test_utils::DragAndDropOperation& op) {
+ return op.type() == ui_test_utils::DragAndDropOperation::Type::Move;
+ });
+ return move_operations;
+}
+
+class ScopedCGEventsEnabler {
tapted 2016/05/23 07:29:28 comment this, e.g. // While in scope, causes even
themblsha 2016/05/26 15:13:25 Done.
+ public:
+ ScopedCGEventsEnabler()
+ : enable_cgevents_(ui_controls::SendMouseEventsAsCGEvents()) {
+ ui_controls::SetSendMouseEventsAsCGEvents(true);
+ }
+
+ ~ScopedCGEventsEnabler() {
+ ui_controls::SetSendMouseEventsAsCGEvents(enable_cgevents_);
+ }
+
+ private:
+ bool enable_cgevents_;
tapted 2016/05/23 07:29:28 Is this member needed? (i.e. do we need the comple
themblsha 2016/05/26 15:13:25 No, I guess I was trying to write a generic versio
+};
tapted 2016/05/23 07:29:28 nit: DISALLOW_COPY_AND_ASSIGN(..)
+
+class BlockRunner : public base::DelegateSimpleThread::Delegate {
tapted 2016/05/23 07:29:27 needs a comment
+ public:
+ BlockRunner(void (^block)(), const base::Closure& quit_closure)
+ : block_(block),
+ quit_closure_(quit_closure),
+ task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+ ~BlockRunner() override {}
tapted 2016/05/23 07:29:27 remove this? I don't think it's needed
+
+ void Run() override {
+ std::unique_ptr<base::MessageLoop> loop(
tapted 2016/05/23 07:29:28 Can this just be on the stack? base::MessageLoop
themblsha 2016/05/26 15:13:25 It can, thanks :)
+ new base::MessageLoop(base::MessageLoop::TYPE_DEFAULT));
+
+ block_();
+
+ task_runner_->PostTask(FROM_HERE, quit_closure_);
+ }
+
+ private:
+ void (^block_)();
+ base::Closure quit_closure_;
+ scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
+};
tapted 2016/05/23 07:29:28 nit: DISALLOW_COPY_AND_ASSIGN
+
+void RunAtBackgroundQueue(void (^block)()) {
+ DCHECK_EQ(dispatch_get_current_queue(), dispatch_get_main_queue());
+ base::RunLoop runner;
+
+ BlockRunner thread_runner(block, runner.QuitClosure());
+ base::DelegateSimpleThread thread(&thread_runner,
+ "interactive_test_utils.BackgroundQueue");
+ thread.Start();
+
+ // We need to run the loop on the main thread, so the mouse events will be
+ // actually processed.
+ runner.Run();
+
+ thread.Join();
+}
+
+bool WindowIsMoving(NSWindow* window) {
tapted 2016/05/23 07:29:28 Perhaps WindowHasActiveDragger
+ views::BridgedNativeWidget* bridge =
+ views::NativeWidgetMac::GetBridgeForNativeWindow(window);
+ DCHECK(bridge);
+ return bridge->IsRunMoveLoopActive();
tapted 2016/05/23 07:29:28 It's not good to couple together this file with Br
+}
+
+// Returns true if window under the cursor is currently moving by WindowServer.
+bool WindowIsMovingBySystem() {
tapted 2016/05/23 07:29:28 `BySystem` is not accurate any longer. Perhaps Win
+ NSWindow* window = WindowFinder().GetLocalProcessWindowAtPoint(
+ views::EventMonitor::GetLastMouseLocation(),
tapted 2016/05/23 07:29:28 Use gfx::ScreenPointFromNSPoint([NSEvent mouseLoca
+ std::set<gfx::NativeWindow>());
+ return window && WindowIsMoving(window);
+}
+
+// If current window under the cursor is currently moving by WindowServer, wait
+// for the NSWindowMovedEventType notification.
+//
+// Returns whether the window under the cursor was moved by the system.
+bool WaitForSystemWindowMoveToStop() {
+ // NOTE: This is a potentially troublesome part, currently it only works
+ // with MacViewsBrowser when the moved window entered RunMoveLoop.
+ // On non-MacViewsBrowser builds or when the window was moved using the
+ // caption we would be unable to detect that the window was moved using the
+ // WindowServer and would not wait for the final NSWindowMovedEventType
+ // notification.
+ //
+ // As a possible solution it would be possible to add an
+ // additional argument to the function
+ // |force_wait_for_system_window_move_to_finish|, and force waiting for
+ // notification if this ever becomes a problem.
+ NSWindow* window = WindowFinder().GetLocalProcessWindowAtPoint(
+ views::EventMonitor::GetLastMouseLocation(),
+ std::set<gfx::NativeWindow>());
+ const bool window_is_moving_by_system = window && WindowIsMoving(window);
+
+ if (WindowIsMovingBySystem()) {
+ // Wait for a final NSWindowMovedEventType notification, otherwise
+ // the window won't be in the final position. It arrives asynchronously
+ // after the mouse move events.
+ NSEvent* window_move_event =
+ [NSEvent otherEventWithType:NSAppKitDefined
+ location:NSZeroPoint
+ modifierFlags:0
+ timestamp:0
+ windowNumber:0
+ context:0
+ subtype:NSWindowMovedEventType
+ data1:0
+ data2:0];
+ base::RunLoop no_window_move_runner;
+ ui_controls::NotifyWhenEventIsProcessed(
+ window_move_event, no_window_move_runner.QuitClosure());
+ no_window_move_runner.Run();
+ }
+
+ return window_is_moving_by_system;
+}
+
+} // namespace
namespace ui_test_utils {
tapted 2016/05/23 07:29:28 move this to the top of the file before the anonym
themblsha 2016/05/26 15:13:25 Done. Also removed the unneeded functions you've a
@@ -44,4 +214,117 @@ bool ShowAndFocusNativeWindow(gfx::NativeWindow window) {
return !async_waiter || notification_observed;
}
+void DragAndDrop(const std::list<DragAndDropOperation>& operations) {
+ const bool should_be_moved_by_system =
+ DragAndDropMoveOperations(operations).size() > 2;
+
+ ScopedCGEventsEnabler cgevents_enabler;
+
+ RunAtBackgroundQueue(^() {
+ std::list<DragAndDropOperation> mutable_operations(operations);
+ bool window_was_moved_by_system = false;
+
+ while (!mutable_operations.empty()) {
tapted 2016/05/23 07:29:28 This can just iterate normally - I don't see any n
themblsha 2016/05/26 15:13:25 I think the code is simpler with a list, see the r
+ DragAndDropOperation op = mutable_operations.front();
+ mutable_operations.pop_front();
+ const bool last_operation = mutable_operations.empty();
+ const bool have_remaining_move_operations =
+ !DragAndDropMoveOperations(mutable_operations).empty();
+
+ switch (op.type()) {
+ case DragAndDropOperation::Type::Move:
+ case DragAndDropOperation::Type::MoveWithoutAck:
+ MouseMove(op.point(), op.delay());
+ // During the drag a new window could be both detached and reattached,
+ // and if we check for WindowIsMovingBySystem() only at the very end,
+ // it will return false, as the original window was stationary.
+ window_was_moved_by_system |= WindowIsMovingBySystem();
+
+ if (!have_remaining_move_operations) {
+ // WaitForSystemWindowMoveToStop() is necessary to make sure window
+ // frame is final after the drag-n-drop operation.
+ window_was_moved_by_system |= WaitForSystemWindowMoveToStop();
+ DCHECK_EQ(window_was_moved_by_system, should_be_moved_by_system);
+ }
+ break;
+ case DragAndDropOperation::Type::MouseDown:
+ MouseDown();
+ break;
+ case DragAndDropOperation::Type::MouseUp:
+ MouseUp();
+
+ if (last_operation) {
+ DCHECK(!WindowIsMovingBySystem());
+ }
+ break;
+ case DragAndDropOperation::Type::SetMousePositionOverride:
+ ui_controls::SetMousePositionOverride(true, op.point());
+ break;
+ case DragAndDropOperation::Type::UnsetMousePositionOverride:
+ ui_controls::SetMousePositionOverride(false, gfx::Point());
+ break;
+ case DragAndDropOperation::Type::DebugDelay:
+ usleep(op.delay().InMicroseconds());
+ break;
+ }
+ }
+ });
+}
+
+void DragAndDrop(const gfx::Point& from, const gfx::Point& to, int steps) {
+ DCHECK_GE(steps, 1);
+ std::list<DragAndDropOperation> operations;
+ operations.push_back(DragAndDropOperation::Move(from));
+ operations.push_back(DragAndDropOperation::MouseDown());
+
+ for (int i = 1; i <= steps; ++i) {
+ const double progress = static_cast<double>(i) / steps;
+ operations.push_back(DragAndDropOperation::Move(
+ gfx::Point(gfx::Tween::IntValueBetween(progress, from.x(), to.x()),
+ gfx::Tween::IntValueBetween(progress, from.y(), to.y()))));
+ }
+
+ operations.push_back(DragAndDropOperation::MouseUp());
+ DragAndDrop(operations);
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::Move(const gfx::Point& p) {
+ return DragAndDropOperation(Type::Move, p);
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::MoveWithoutAck(
+ const gfx::Point& p,
+ const base::TimeDelta& delay) {
+ return DragAndDropOperation(Type::MoveWithoutAck, p, delay);
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::MouseDown() {
+ return DragAndDropOperation(Type::MouseDown, gfx::Point());
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::MouseUp() {
+ return DragAndDropOperation(Type::MouseUp, gfx::Point());
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::SetMousePositionOverride(
+ const gfx::Point& p) {
+ return DragAndDropOperation(Type::SetMousePositionOverride, p);
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::UnsetMousePositionOverride() {
+ return DragAndDropOperation(Type::UnsetMousePositionOverride, gfx::Point());
+}
+
+// static
+DragAndDropOperation DragAndDropOperation::DebugDelay() {
+ return DragAndDropOperation(Type::DebugDelay, gfx::Point(),
+ base::TimeDelta::FromSeconds(1));
+}
+
} // namespace ui_test_utils

Powered by Google App Engine
This is Rietveld 408576698