| 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..9b0be72da34f188172654c5724ed755677ce8ecd 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/thread_task_runner_handle.h"
|
| +#include "base/threading/simple_thread.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)) {
|
| + base::RunLoop runner;
|
| + bool result = block(runner.QuitClosure());
|
| + runner.Run();
|
| + return result;
|
| +}
|
| +
|
| +bool MouseMove(const gfx::Point& p,
|
| + const base::TimeDelta& delay = base::TimeDelta()) {
|
| + 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(
|
| + 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 {
|
| + public:
|
| + ScopedCGEventsEnabler()
|
| + : enable_cgevents_(ui_controls::SendMouseEventsAsCGEvents()) {
|
| + ui_controls::SetSendMouseEventsAsCGEvents(true);
|
| + }
|
| +
|
| + ~ScopedCGEventsEnabler() {
|
| + ui_controls::SetSendMouseEventsAsCGEvents(enable_cgevents_);
|
| + }
|
| +
|
| + private:
|
| + bool enable_cgevents_;
|
| +};
|
| +
|
| +class BlockRunner : public base::DelegateSimpleThread::Delegate {
|
| + public:
|
| + BlockRunner(void (^block)(), const base::Closure& quit_closure)
|
| + : block_(block),
|
| + quit_closure_(quit_closure),
|
| + task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
|
| + ~BlockRunner() override {}
|
| +
|
| + void Run() override {
|
| + scoped_ptr<base::MessageLoop> loop(
|
| + 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_;
|
| +};
|
| +
|
| +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) {
|
| + views::BridgedNativeWidget* bridge =
|
| + views::NativeWidgetMac::GetBridgeForNativeWindow(window);
|
| + DCHECK(bridge);
|
| + return bridge->IsRunMoveLoopActive();
|
| +}
|
| +
|
| +// Returns true if window under the cursor is currently moving by WindowServer.
|
| +bool WindowIsMovingBySystem() {
|
| + NSWindow* window = WindowFinder().GetLocalProcessWindowAtPoint(
|
| + views::EventMonitor::GetLastMouseLocation(),
|
| + 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 {
|
|
|
| @@ -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()) {
|
| + 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
|
|
|