adjust browser_tests exclusion list for Dr.Memory bots
[chromium-blink-merge.git] / ash / drag_drop / drag_drop_controller.cc
blob4a2603f05309c9bef34dc3df07db606225f6e5a9
1 // Copyright (c) 2012 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.
5 #include "ash/drag_drop/drag_drop_controller.h"
7 #include "ash/drag_drop/drag_drop_tracker.h"
8 #include "ash/drag_drop/drag_image_view.h"
9 #include "ash/shell.h"
10 #include "ash/wm/coordinate_conversion.h"
11 #include "base/bind.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/run_loop.h"
14 #include "ui/aura/client/capture_client.h"
15 #include "ui/aura/env.h"
16 #include "ui/aura/window.h"
17 #include "ui/aura/window_delegate.h"
18 #include "ui/aura/window_event_dispatcher.h"
19 #include "ui/base/dragdrop/drag_drop_types.h"
20 #include "ui/base/dragdrop/os_exchange_data.h"
21 #include "ui/base/hit_test.h"
22 #include "ui/events/event.h"
23 #include "ui/events/event_utils.h"
24 #include "ui/gfx/animation/linear_animation.h"
25 #include "ui/gfx/path.h"
26 #include "ui/gfx/point.h"
27 #include "ui/gfx/rect.h"
28 #include "ui/gfx/rect_conversions.h"
29 #include "ui/views/views_delegate.h"
30 #include "ui/views/widget/native_widget_aura.h"
31 #include "ui/wm/public/drag_drop_delegate.h"
33 namespace ash {
34 namespace {
36 // The duration of the drag cancel animation in millisecond.
37 const int kCancelAnimationDuration = 250;
38 const int kTouchCancelAnimationDuration = 20;
39 // The frame rate of the drag cancel animation in hertz.
40 const int kCancelAnimationFrameRate = 60;
42 // For touch initiated dragging, we scale and shift drag image by the following:
43 static const float kTouchDragImageScale = 1.2f;
44 static const int kTouchDragImageVerticalOffset = -25;
46 // Adjusts the drag image bounds such that the new bounds are scaled by |scale|
47 // and translated by the |drag_image_offset| and and additional
48 // |vertical_offset|.
49 gfx::Rect AdjustDragImageBoundsForScaleAndOffset(
50 const gfx::Rect& drag_image_bounds,
51 int vertical_offset,
52 float scale,
53 gfx::Vector2d* drag_image_offset) {
54 gfx::PointF final_origin = drag_image_bounds.origin();
55 gfx::SizeF final_size = drag_image_bounds.size();
56 final_size.Scale(scale);
57 drag_image_offset->set_x(drag_image_offset->x() * scale);
58 drag_image_offset->set_y(drag_image_offset->y() * scale);
59 float total_x_offset = drag_image_offset->x();
60 float total_y_offset = drag_image_offset->y() - vertical_offset;
61 final_origin.Offset(-total_x_offset, -total_y_offset);
62 return gfx::ToEnclosingRect(gfx::RectF(final_origin, final_size));
65 void DispatchGestureEndToWindow(aura::Window* window) {
66 if (window && window->delegate()) {
67 ui::GestureEvent gesture_end(
68 ui::ET_GESTURE_END,
72 ui::EventTimeForNow(),
73 ui::GestureEventDetails(ui::ET_GESTURE_END, 0, 0),
74 0);
75 window->delegate()->OnGestureEvent(&gesture_end);
78 } // namespace
80 class DragDropTrackerDelegate : public aura::WindowDelegate {
81 public:
82 explicit DragDropTrackerDelegate(DragDropController* controller)
83 : drag_drop_controller_(controller) {}
84 virtual ~DragDropTrackerDelegate() {}
86 // Overridden from WindowDelegate:
87 virtual gfx::Size GetMinimumSize() const OVERRIDE {
88 return gfx::Size();
91 virtual gfx::Size GetMaximumSize() const OVERRIDE {
92 return gfx::Size();
95 virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
96 const gfx::Rect& new_bounds) OVERRIDE {}
97 virtual gfx::NativeCursor GetCursor(const gfx::Point& point) OVERRIDE {
98 return gfx::kNullCursor;
100 virtual int GetNonClientComponent(const gfx::Point& point) const OVERRIDE {
101 return HTCAPTION;
103 virtual bool ShouldDescendIntoChildForEventHandling(
104 aura::Window* child,
105 const gfx::Point& location) OVERRIDE {
106 return true;
108 virtual bool CanFocus() OVERRIDE { return true; }
109 virtual void OnCaptureLost() OVERRIDE {
110 if (drag_drop_controller_->IsDragDropInProgress())
111 drag_drop_controller_->DragCancel();
113 virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
115 virtual void OnDeviceScaleFactorChanged(float device_scale_factor) OVERRIDE {}
116 virtual void OnWindowDestroying(aura::Window* window) OVERRIDE {}
117 virtual void OnWindowDestroyed(aura::Window* window) OVERRIDE {}
118 virtual void OnWindowTargetVisibilityChanged(bool visible) OVERRIDE {}
119 virtual bool HasHitTestMask() const OVERRIDE {
120 return true;
122 virtual void GetHitTestMask(gfx::Path* mask) const OVERRIDE {
123 DCHECK(mask->isEmpty());
126 private:
127 DragDropController* drag_drop_controller_;
129 DISALLOW_COPY_AND_ASSIGN(DragDropTrackerDelegate);
132 ////////////////////////////////////////////////////////////////////////////////
133 // DragDropController, public:
135 DragDropController::DragDropController()
136 : drag_data_(NULL),
137 drag_operation_(0),
138 drag_window_(NULL),
139 drag_source_window_(NULL),
140 should_block_during_drag_drop_(true),
141 drag_drop_window_delegate_(new DragDropTrackerDelegate(this)),
142 current_drag_event_source_(ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE),
143 weak_factory_(this) {
144 Shell::GetInstance()->PrependPreTargetHandler(this);
147 DragDropController::~DragDropController() {
148 Shell::GetInstance()->RemovePreTargetHandler(this);
149 Cleanup();
150 if (cancel_animation_)
151 cancel_animation_->End();
152 if (drag_image_)
153 drag_image_.reset();
156 int DragDropController::StartDragAndDrop(
157 const ui::OSExchangeData& data,
158 aura::Window* root_window,
159 aura::Window* source_window,
160 const gfx::Point& root_location,
161 int operation,
162 ui::DragDropTypes::DragEventSource source) {
163 if (IsDragDropInProgress())
164 return 0;
166 const ui::OSExchangeData::Provider* provider = &data.provider();
167 // We do not support touch drag/drop without a drag image.
168 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH &&
169 provider->GetDragImage().size().IsEmpty())
170 return 0;
172 current_drag_event_source_ = source;
173 DragDropTracker* tracker =
174 new DragDropTracker(root_window, drag_drop_window_delegate_.get());
175 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
176 // We need to transfer the current gesture sequence and the GR's touch event
177 // queue to the |drag_drop_tracker_|'s capture window so that when it takes
178 // capture, it still gets a valid gesture state.
179 ui::GestureRecognizer::Get()->TransferEventsTo(source_window,
180 tracker->capture_window());
181 // We also send a gesture end to the source window so it can clear state.
182 // TODO(varunjain): Remove this whole block when gesture sequence
183 // transferring is properly done in the GR (http://crbug.com/160558)
184 DispatchGestureEndToWindow(source_window);
186 tracker->TakeCapture();
187 drag_drop_tracker_.reset(tracker);
188 drag_source_window_ = source_window;
189 if (drag_source_window_)
190 drag_source_window_->AddObserver(this);
191 pending_long_tap_.reset();
193 drag_data_ = &data;
194 drag_operation_ = operation;
196 float drag_image_scale = 1;
197 int drag_image_vertical_offset = 0;
198 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
199 drag_image_scale = kTouchDragImageScale;
200 drag_image_vertical_offset = kTouchDragImageVerticalOffset;
202 gfx::Point start_location = root_location;
203 ash::wm::ConvertPointToScreen(root_window, &start_location);
204 drag_image_final_bounds_for_cancel_animation_ = gfx::Rect(
205 start_location - provider->GetDragImageOffset(),
206 provider->GetDragImage().size());
207 drag_image_.reset(new DragImageView(source_window->GetRootWindow(), source));
208 drag_image_->SetImage(provider->GetDragImage());
209 drag_image_offset_ = provider->GetDragImageOffset();
210 gfx::Rect drag_image_bounds(start_location, drag_image_->GetPreferredSize());
211 drag_image_bounds = AdjustDragImageBoundsForScaleAndOffset(drag_image_bounds,
212 drag_image_vertical_offset, drag_image_scale, &drag_image_offset_);
213 drag_image_->SetBoundsInScreen(drag_image_bounds);
214 drag_image_->SetWidgetVisible(true);
215 if (source == ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH) {
216 drag_image_->SetTouchDragOperationHintPosition(gfx::Point(
217 drag_image_offset_.x(),
218 drag_image_offset_.y() + drag_image_vertical_offset));
221 drag_window_ = NULL;
223 // Ends cancel animation if it's in progress.
224 if (cancel_animation_)
225 cancel_animation_->End();
227 if (should_block_during_drag_drop_) {
228 base::RunLoop run_loop;
229 quit_closure_ = run_loop.QuitClosure();
230 base::MessageLoopForUI* loop = base::MessageLoopForUI::current();
231 base::MessageLoop::ScopedNestableTaskAllower allow_nested(loop);
232 run_loop.Run();
235 if (!cancel_animation_.get() || !cancel_animation_->is_animating() ||
236 !pending_long_tap_.get()) {
237 // If drag cancel animation is running, this cleanup is done when the
238 // animation completes.
239 if (drag_source_window_)
240 drag_source_window_->RemoveObserver(this);
241 drag_source_window_ = NULL;
244 return drag_operation_;
247 void DragDropController::DragUpdate(aura::Window* target,
248 const ui::LocatedEvent& event) {
249 aura::client::DragDropDelegate* delegate = NULL;
250 int op = ui::DragDropTypes::DRAG_NONE;
251 if (target != drag_window_) {
252 if (drag_window_) {
253 if ((delegate = aura::client::GetDragDropDelegate(drag_window_)))
254 delegate->OnDragExited();
255 if (drag_window_ != drag_source_window_)
256 drag_window_->RemoveObserver(this);
258 drag_window_ = target;
259 // We are already an observer of |drag_source_window_| so no need to add.
260 if (drag_window_ != drag_source_window_)
261 drag_window_->AddObserver(this);
262 if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
263 ui::DropTargetEvent e(*drag_data_,
264 event.location(),
265 event.root_location(),
266 drag_operation_);
267 e.set_flags(event.flags());
268 delegate->OnDragEntered(e);
270 } else {
271 if ((delegate = aura::client::GetDragDropDelegate(drag_window_))) {
272 ui::DropTargetEvent e(*drag_data_,
273 event.location(),
274 event.root_location(),
275 drag_operation_);
276 e.set_flags(event.flags());
277 op = delegate->OnDragUpdated(e);
278 gfx::NativeCursor cursor = ui::kCursorNoDrop;
279 if (op & ui::DragDropTypes::DRAG_COPY)
280 cursor = ui::kCursorCopy;
281 else if (op & ui::DragDropTypes::DRAG_LINK)
282 cursor = ui::kCursorAlias;
283 else if (op & ui::DragDropTypes::DRAG_MOVE)
284 cursor = ui::kCursorGrabbing;
285 ash::Shell::GetInstance()->cursor_manager()->SetCursor(cursor);
289 DCHECK(drag_image_.get());
290 if (drag_image_->visible()) {
291 gfx::Point root_location_in_screen = event.root_location();
292 ash::wm::ConvertPointToScreen(target->GetRootWindow(),
293 &root_location_in_screen);
294 drag_image_->SetScreenPosition(
295 root_location_in_screen - drag_image_offset_);
296 drag_image_->SetTouchDragOperation(op);
300 void DragDropController::Drop(aura::Window* target,
301 const ui::LocatedEvent& event) {
302 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
303 aura::client::DragDropDelegate* delegate = NULL;
305 // We must guarantee that a target gets a OnDragEntered before Drop. WebKit
306 // depends on not getting a Drop without DragEnter. This behavior is
307 // consistent with drag/drop on other platforms.
308 if (target != drag_window_)
309 DragUpdate(target, event);
310 DCHECK(target == drag_window_);
312 if ((delegate = aura::client::GetDragDropDelegate(target))) {
313 ui::DropTargetEvent e(
314 *drag_data_, event.location(), event.root_location(), drag_operation_);
315 e.set_flags(event.flags());
316 drag_operation_ = delegate->OnPerformDrop(e);
317 if (drag_operation_ == 0)
318 StartCanceledAnimation(kCancelAnimationDuration);
319 else
320 drag_image_.reset();
321 } else {
322 drag_image_.reset();
325 Cleanup();
326 if (should_block_during_drag_drop_)
327 quit_closure_.Run();
330 void DragDropController::DragCancel() {
331 DoDragCancel(kCancelAnimationDuration);
334 bool DragDropController::IsDragDropInProgress() {
335 return !!drag_drop_tracker_.get();
338 void DragDropController::OnKeyEvent(ui::KeyEvent* event) {
339 if (IsDragDropInProgress() && event->key_code() == ui::VKEY_ESCAPE) {
340 DragCancel();
341 event->StopPropagation();
345 void DragDropController::OnMouseEvent(ui::MouseEvent* event) {
346 if (!IsDragDropInProgress())
347 return;
349 // If current drag session was not started by mouse, dont process this mouse
350 // event, but consume it so it does not interfere with current drag session.
351 if (current_drag_event_source_ !=
352 ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE) {
353 event->StopPropagation();
354 return;
357 aura::Window* translated_target = drag_drop_tracker_->GetTarget(*event);
358 if (!translated_target) {
359 DragCancel();
360 event->StopPropagation();
361 return;
363 scoped_ptr<ui::LocatedEvent> translated_event(
364 drag_drop_tracker_->ConvertEvent(translated_target, *event));
365 switch (translated_event->type()) {
366 case ui::ET_MOUSE_DRAGGED:
367 DragUpdate(translated_target, *translated_event.get());
368 break;
369 case ui::ET_MOUSE_RELEASED:
370 Drop(translated_target, *translated_event.get());
371 break;
372 default:
373 // We could also reach here because RootWindow may sometimes generate a
374 // bunch of fake mouse events
375 // (aura::RootWindow::PostMouseMoveEventAfterWindowChange).
376 break;
378 event->StopPropagation();
381 void DragDropController::OnTouchEvent(ui::TouchEvent* event) {
382 if (!IsDragDropInProgress())
383 return;
385 // If current drag session was not started by touch, dont process this touch
386 // event, but consume it so it does not interfere with current drag session.
387 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
388 event->StopPropagation();
390 if (event->handled())
391 return;
393 if (event->type() == ui::ET_TOUCH_CANCELLED)
394 DragCancel();
397 void DragDropController::OnGestureEvent(ui::GestureEvent* event) {
398 if (!IsDragDropInProgress())
399 return;
401 // No one else should handle gesture events when in drag drop. Note that it is
402 // not enough to just set ER_HANDLED because the dispatcher only stops
403 // dispatching when the event has ER_CONSUMED. If we just set ER_HANDLED, the
404 // event will still be dispatched to other handlers and we depend on
405 // individual handlers' kindness to not touch events marked ER_HANDLED (not
406 // all handlers are so kind and may cause bugs like crbug.com/236493).
407 event->StopPropagation();
409 // If current drag session was not started by touch, dont process this event.
410 if (current_drag_event_source_ != ui::DragDropTypes::DRAG_EVENT_SOURCE_TOUCH)
411 return;
413 // Apply kTouchDragImageVerticalOffset to the location.
414 ui::GestureEvent touch_offset_event(*event,
415 static_cast<aura::Window*>(NULL),
416 static_cast<aura::Window*>(NULL));
417 gfx::Point touch_offset_location = touch_offset_event.location();
418 gfx::Point touch_offset_root_location = touch_offset_event.root_location();
419 touch_offset_location.Offset(0, kTouchDragImageVerticalOffset);
420 touch_offset_root_location.Offset(0, kTouchDragImageVerticalOffset);
421 touch_offset_event.set_location(touch_offset_location);
422 touch_offset_event.set_root_location(touch_offset_root_location);
424 aura::Window* translated_target =
425 drag_drop_tracker_->GetTarget(touch_offset_event);
426 if (!translated_target) {
427 DragCancel();
428 event->SetHandled();
429 return;
431 scoped_ptr<ui::LocatedEvent> translated_event(
432 drag_drop_tracker_->ConvertEvent(translated_target, touch_offset_event));
434 switch (event->type()) {
435 case ui::ET_GESTURE_SCROLL_UPDATE:
436 DragUpdate(translated_target, *translated_event.get());
437 break;
438 case ui::ET_GESTURE_SCROLL_END:
439 Drop(translated_target, *translated_event.get());
440 break;
441 case ui::ET_SCROLL_FLING_START:
442 case ui::ET_GESTURE_LONG_TAP:
443 // Ideally we would want to just forward this long tap event to the
444 // |drag_source_window_|. However, webkit does not accept events while a
445 // drag drop is still in progress. The drag drop ends only when the nested
446 // message loop ends. Due to this stupidity, we have to defer forwarding
447 // the long tap.
448 pending_long_tap_.reset(
449 new ui::GestureEvent(*event,
450 static_cast<aura::Window*>(drag_drop_tracker_->capture_window()),
451 static_cast<aura::Window*>(drag_source_window_)));
452 DoDragCancel(kTouchCancelAnimationDuration);
453 break;
454 default:
455 break;
457 event->SetHandled();
460 void DragDropController::OnWindowDestroyed(aura::Window* window) {
461 if (drag_window_ == window)
462 drag_window_ = NULL;
463 if (drag_source_window_ == window)
464 drag_source_window_ = NULL;
467 ////////////////////////////////////////////////////////////////////////////////
468 // DragDropController, protected:
470 gfx::LinearAnimation* DragDropController::CreateCancelAnimation(
471 int duration,
472 int frame_rate,
473 gfx::AnimationDelegate* delegate) {
474 return new gfx::LinearAnimation(duration, frame_rate, delegate);
477 ////////////////////////////////////////////////////////////////////////////////
478 // DragDropController, private:
480 void DragDropController::AnimationEnded(const gfx::Animation* animation) {
481 cancel_animation_.reset();
483 // By the time we finish animation, another drag/drop session may have
484 // started. We do not want to destroy the drag image in that case.
485 if (!IsDragDropInProgress())
486 drag_image_.reset();
487 if (pending_long_tap_) {
488 // If not in a nested message loop, we can forward the long tap right now.
489 if (!should_block_during_drag_drop_)
490 ForwardPendingLongTap();
491 else {
492 // See comment about this in OnGestureEvent().
493 base::MessageLoopForUI::current()->PostTask(
494 FROM_HERE,
495 base::Bind(&DragDropController::ForwardPendingLongTap,
496 weak_factory_.GetWeakPtr()));
501 void DragDropController::DoDragCancel(int drag_cancel_animation_duration_ms) {
502 ash::Shell::GetInstance()->cursor_manager()->SetCursor(ui::kCursorPointer);
504 // |drag_window_| can be NULL if we have just started the drag and have not
505 // received any DragUpdates, or, if the |drag_window_| gets destroyed during
506 // a drag/drop.
507 aura::client::DragDropDelegate* delegate = drag_window_?
508 aura::client::GetDragDropDelegate(drag_window_) : NULL;
509 if (delegate)
510 delegate->OnDragExited();
512 Cleanup();
513 drag_operation_ = 0;
514 StartCanceledAnimation(drag_cancel_animation_duration_ms);
515 if (should_block_during_drag_drop_)
516 quit_closure_.Run();
519 void DragDropController::AnimationProgressed(const gfx::Animation* animation) {
520 gfx::Rect current_bounds = animation->CurrentValueBetween(
521 drag_image_initial_bounds_for_cancel_animation_,
522 drag_image_final_bounds_for_cancel_animation_);
523 drag_image_->SetBoundsInScreen(current_bounds);
526 void DragDropController::AnimationCanceled(const gfx::Animation* animation) {
527 AnimationEnded(animation);
530 void DragDropController::StartCanceledAnimation(int animation_duration_ms) {
531 DCHECK(drag_image_.get());
532 drag_image_->SetTouchDragOperationHintOff();
533 drag_image_initial_bounds_for_cancel_animation_ =
534 drag_image_->GetBoundsInScreen();
535 cancel_animation_.reset(CreateCancelAnimation(animation_duration_ms,
536 kCancelAnimationFrameRate,
537 this));
538 cancel_animation_->Start();
541 void DragDropController::ForwardPendingLongTap() {
542 if (drag_source_window_ && drag_source_window_->delegate()) {
543 drag_source_window_->delegate()->OnGestureEvent(pending_long_tap_.get());
544 DispatchGestureEndToWindow(drag_source_window_);
546 pending_long_tap_.reset();
547 if (drag_source_window_)
548 drag_source_window_->RemoveObserver(this);
549 drag_source_window_ = NULL;
552 void DragDropController::Cleanup() {
553 if (drag_window_)
554 drag_window_->RemoveObserver(this);
555 drag_window_ = NULL;
556 drag_data_ = NULL;
557 // Cleanup can be called again while deleting DragDropTracker, so use Pass
558 // instead of reset to avoid double free.
559 drag_drop_tracker_.Pass();
562 } // namespace ash