tab_drag_controller.cc revision 010d83a9304c5a91596085d917d248abff47903a
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.
4
5#include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
6
7#include <math.h>
8#include <set>
9
10#include "base/auto_reset.h"
11#include "base/callback.h"
12#include "base/command_line.h"
13#include "base/i18n/rtl.h"
14#include "chrome/browser/chrome_notification_types.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/ui/app_modal_dialogs/javascript_dialog_manager.h"
17#include "chrome/browser/ui/browser_list.h"
18#include "chrome/browser/ui/browser_window.h"
19#include "chrome/browser/ui/media_utils.h"
20#include "chrome/browser/ui/tabs/tab_strip_model.h"
21#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
22#include "chrome/browser/ui/views/frame/browser_view.h"
23#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
24#include "chrome/browser/ui/views/tabs/stacked_tab_strip_layout.h"
25#include "chrome/browser/ui/views/tabs/tab.h"
26#include "chrome/browser/ui/views/tabs/tab_strip.h"
27#include "chrome/browser/ui/views/tabs/window_finder.h"
28#include "chrome/common/chrome_switches.h"
29#include "content/public/browser/invalidate_type.h"
30#include "content/public/browser/notification_details.h"
31#include "content/public/browser/notification_service.h"
32#include "content/public/browser/notification_source.h"
33#include "content/public/browser/notification_types.h"
34#include "content/public/browser/user_metrics.h"
35#include "content/public/browser/web_contents.h"
36#include "extensions/browser/extension_function_dispatcher.h"
37#include "ui/aura/env.h"
38#include "ui/base/resource/resource_bundle.h"
39#include "ui/events/event_constants.h"
40#include "ui/events/event_utils.h"
41#include "ui/gfx/geometry/point_conversions.h"
42#include "ui/gfx/screen.h"
43#include "ui/views/focus/view_storage.h"
44#include "ui/views/widget/root_view.h"
45#include "ui/views/widget/widget.h"
46#include "ui/wm/core/window_modality_controller.h"
47
48#if defined(USE_ASH)
49#include "ash/accelerators/accelerator_commands.h"
50#include "ash/wm/coordinate_conversion.h"
51#include "ash/wm/window_state.h"
52#include "ui/aura/env.h"
53#include "ui/aura/window.h"
54#include "ui/aura/window_event_dispatcher.h"
55#include "ui/events/gestures/gesture_recognizer.h"
56#endif
57
58#if defined(OS_WIN)
59#include "ui/aura/window.h"
60#include "ui/events/gestures/gesture_recognizer.h"
61#endif
62
63using base::UserMetricsAction;
64using content::OpenURLParams;
65using content::WebContents;
66
67// If non-null there is a drag underway.
68static TabDragController* instance_ = NULL;
69
70namespace {
71
72// Delay, in ms, during dragging before we bring a window to front.
73const int kBringToFrontDelay = 750;
74
75// Initial delay before moving tabs when the dragged tab is close to the edge of
76// the stacked tabs.
77const int kMoveAttachedInitialDelay = 600;
78
79// Delay for moving tabs after the initial delay has passed.
80const int kMoveAttachedSubsequentDelay = 300;
81
82const int kHorizontalMoveThreshold = 16;  // Pixels.
83
84// Distance from the next/previous stacked before before we consider the tab
85// close enough to trigger moving.
86const int kStackedDistance = 36;
87
88void SetWindowPositionManaged(gfx::NativeWindow window, bool value) {
89#if defined(USE_ASH)
90  ash::wm::GetWindowState(window)->set_window_position_managed(value);
91#endif
92}
93
94// Returns true if |tab_strip| browser window is docked.
95bool IsDockedOrSnapped(const TabStrip* tab_strip) {
96#if defined(USE_ASH)
97  DCHECK(tab_strip);
98  ash::wm::WindowState* window_state =
99      ash::wm::GetWindowState(tab_strip->GetWidget()->GetNativeWindow());
100  return window_state->IsDocked() || window_state->IsSnapped();
101#else
102  return false;
103#endif
104}
105
106// Returns true if |bounds| contains the y-coordinate |y|. The y-coordinate
107// of |bounds| is adjusted by |vertical_adjustment|.
108bool DoesRectContainVerticalPointExpanded(
109    const gfx::Rect& bounds,
110    int vertical_adjustment,
111    int y) {
112  int upper_threshold = bounds.bottom() + vertical_adjustment;
113  int lower_threshold = bounds.y() - vertical_adjustment;
114  return y >= lower_threshold && y <= upper_threshold;
115}
116
117// Adds |x_offset| to all the rectangles in |rects|.
118void OffsetX(int x_offset, std::vector<gfx::Rect>* rects) {
119  if (x_offset == 0)
120    return;
121
122  for (size_t i = 0; i < rects->size(); ++i)
123    (*rects)[i].set_x((*rects)[i].x() + x_offset);
124}
125
126// WidgetObserver implementation that resets the window position managed
127// property on Show.
128// We're forced to do this here since BrowserFrameAsh resets the 'window
129// position managed' property during a show and we need the property set to
130// false before WorkspaceLayoutManager sees the visibility change.
131class WindowPositionManagedUpdater : public views::WidgetObserver {
132 public:
133  virtual void OnWidgetVisibilityChanged(views::Widget* widget,
134                                         bool visible) OVERRIDE {
135    SetWindowPositionManaged(widget->GetNativeView(), false);
136  }
137};
138
139// EscapeTracker installs itself as a pre-target handler on aura::Env and runs a
140// callback when it receives the escape key.
141class EscapeTracker : public ui::EventHandler {
142 public:
143  explicit EscapeTracker(const base::Closure& callback)
144      : escape_callback_(callback) {
145    aura::Env::GetInstance()->AddPreTargetHandler(this);
146  }
147
148  virtual ~EscapeTracker() {
149    aura::Env::GetInstance()->RemovePreTargetHandler(this);
150  }
151
152 private:
153  // ui::EventHandler:
154  virtual void OnKeyEvent(ui::KeyEvent* key) OVERRIDE {
155    if (key->type() == ui::ET_KEY_PRESSED &&
156        key->key_code() == ui::VKEY_ESCAPE) {
157      escape_callback_.Run();
158    }
159  }
160
161  base::Closure escape_callback_;
162
163  DISALLOW_COPY_AND_ASSIGN(EscapeTracker);
164};
165
166}  // namespace
167
168TabDragController::TabDragData::TabDragData()
169    : contents(NULL),
170      original_delegate(NULL),
171      source_model_index(-1),
172      attached_tab(NULL),
173      pinned(false) {
174}
175
176TabDragController::TabDragData::~TabDragData() {
177}
178
179///////////////////////////////////////////////////////////////////////////////
180// TabDragController, public:
181
182// static
183const int TabDragController::kTouchVerticalDetachMagnetism = 50;
184
185// static
186const int TabDragController::kVerticalDetachMagnetism = 15;
187
188TabDragController::TabDragController()
189    : detach_into_browser_(true),
190      event_source_(EVENT_SOURCE_MOUSE),
191      source_tabstrip_(NULL),
192      attached_tabstrip_(NULL),
193      screen_(NULL),
194      host_desktop_type_(chrome::HOST_DESKTOP_TYPE_NATIVE),
195      use_aura_capture_policy_(false),
196      offset_to_width_ratio_(0),
197      old_focused_view_id_(
198          views::ViewStorage::GetInstance()->CreateStorageID()),
199      last_move_screen_loc_(0),
200      started_drag_(false),
201      active_(true),
202      source_tab_index_(std::numeric_limits<size_t>::max()),
203      initial_move_(true),
204      detach_behavior_(DETACHABLE),
205      move_behavior_(REORDER),
206      mouse_move_direction_(0),
207      is_dragging_window_(false),
208      is_dragging_new_browser_(false),
209      was_source_maximized_(false),
210      was_source_fullscreen_(false),
211      did_restore_window_(false),
212      end_run_loop_behavior_(END_RUN_LOOP_STOP_DRAGGING),
213      waiting_for_run_loop_to_exit_(false),
214      tab_strip_to_attach_to_after_exit_(NULL),
215      move_loop_widget_(NULL),
216      is_mutating_(false),
217      attach_x_(-1),
218      attach_index_(-1),
219      weak_factory_(this) {
220  instance_ = this;
221}
222
223TabDragController::~TabDragController() {
224  views::ViewStorage::GetInstance()->RemoveView(old_focused_view_id_);
225
226  if (instance_ == this)
227    instance_ = NULL;
228
229  if (move_loop_widget_) {
230    move_loop_widget_->RemoveObserver(this);
231    SetWindowPositionManaged(move_loop_widget_->GetNativeView(), true);
232  }
233
234  if (source_tabstrip_ && detach_into_browser_)
235    GetModel(source_tabstrip_)->RemoveObserver(this);
236
237  // Reset the delegate of the dragged WebContents. This ends up doing nothing
238  // if the drag was completed.
239  if (!detach_into_browser_)
240    ResetDelegates();
241
242  if (event_source_ == EVENT_SOURCE_TOUCH) {
243    TabStrip* capture_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
244        attached_tabstrip_ : source_tabstrip_;
245    capture_tabstrip->GetWidget()->ReleaseCapture();
246  }
247}
248
249void TabDragController::Init(
250    TabStrip* source_tabstrip,
251    Tab* source_tab,
252    const std::vector<Tab*>& tabs,
253    const gfx::Point& mouse_offset,
254    int source_tab_offset,
255    const ui::ListSelectionModel& initial_selection_model,
256    DetachBehavior detach_behavior,
257    MoveBehavior move_behavior,
258    EventSource event_source) {
259  DCHECK(!tabs.empty());
260  DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end());
261  source_tabstrip_ = source_tabstrip;
262  was_source_maximized_ = source_tabstrip->GetWidget()->IsMaximized();
263  was_source_fullscreen_ = source_tabstrip->GetWidget()->IsFullscreen();
264  screen_ = gfx::Screen::GetScreenFor(
265      source_tabstrip->GetWidget()->GetNativeView());
266  host_desktop_type_ = chrome::GetHostDesktopTypeForNativeView(
267      source_tabstrip->GetWidget()->GetNativeView());
268#if defined(OS_LINUX)
269  use_aura_capture_policy_ = true;
270#else
271  use_aura_capture_policy_ =
272      (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH);
273#endif
274  start_point_in_screen_ = gfx::Point(source_tab_offset, mouse_offset.y());
275  views::View::ConvertPointToScreen(source_tab, &start_point_in_screen_);
276  event_source_ = event_source;
277  mouse_offset_ = mouse_offset;
278  detach_behavior_ = detach_behavior;
279  move_behavior_ = move_behavior;
280  last_point_in_screen_ = start_point_in_screen_;
281  last_move_screen_loc_ = start_point_in_screen_.x();
282  initial_tab_positions_ = source_tabstrip->GetTabXCoordinates();
283  if (detach_behavior == NOT_DETACHABLE)
284    detach_into_browser_ = false;
285
286  if (detach_into_browser_)
287    GetModel(source_tabstrip_)->AddObserver(this);
288
289  drag_data_.resize(tabs.size());
290  for (size_t i = 0; i < tabs.size(); ++i)
291    InitTabDragData(tabs[i], &(drag_data_[i]));
292  source_tab_index_ =
293      std::find(tabs.begin(), tabs.end(), source_tab) - tabs.begin();
294
295  // Listen for Esc key presses.
296  escape_tracker_.reset(
297      new EscapeTracker(base::Bind(&TabDragController::EndDrag,
298                                   weak_factory_.GetWeakPtr(),
299                                   END_DRAG_CANCEL)));
300
301  if (source_tab->width() > 0) {
302    offset_to_width_ratio_ = static_cast<float>(
303        source_tab->GetMirroredXInView(source_tab_offset)) /
304        static_cast<float>(source_tab->width());
305  }
306  InitWindowCreatePoint();
307  initial_selection_model_.Copy(initial_selection_model);
308
309  // Gestures don't automatically do a capture. We don't allow multiple drags at
310  // the same time, so we explicitly capture.
311  if (event_source == EVENT_SOURCE_TOUCH)
312    source_tabstrip_->GetWidget()->SetCapture(source_tabstrip_);
313}
314
315// static
316bool TabDragController::IsAttachedTo(const TabStrip* tab_strip) {
317  return (instance_ && instance_->active() &&
318          instance_->attached_tabstrip() == tab_strip);
319}
320
321// static
322bool TabDragController::IsActive() {
323  return instance_ && instance_->active();
324}
325
326void TabDragController::SetMoveBehavior(MoveBehavior behavior) {
327  if (started_drag())
328    return;
329
330  move_behavior_ = behavior;
331}
332
333void TabDragController::Drag(const gfx::Point& point_in_screen) {
334  TRACE_EVENT1("views", "TabDragController::Drag",
335               "point_in_screen", point_in_screen.ToString());
336
337  bring_to_front_timer_.Stop();
338  move_stacked_timer_.Stop();
339
340  if (waiting_for_run_loop_to_exit_)
341    return;
342
343  if (!started_drag_) {
344    if (!CanStartDrag(point_in_screen))
345      return;  // User hasn't dragged far enough yet.
346
347    // On windows SaveFocus() may trigger a capture lost, which destroys us.
348    {
349      base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
350      SaveFocus();
351      if (!ref)
352        return;
353    }
354    started_drag_ = true;
355    Attach(source_tabstrip_, gfx::Point());
356    if (detach_into_browser_ && static_cast<int>(drag_data_.size()) ==
357        GetModel(source_tabstrip_)->count()) {
358      if (was_source_maximized_ || was_source_fullscreen_) {
359        did_restore_window_ = true;
360        // When all tabs in a maximized browser are dragged the browser gets
361        // restored during the drag and maximized back when the drag ends.
362        views::Widget* widget = GetAttachedBrowserWidget();
363        const int last_tabstrip_width = attached_tabstrip_->tab_area_width();
364        std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs();
365        OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds);
366        gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source_tabstrip_,
367                                                           point_in_screen,
368                                                           &drag_bounds));
369        new_bounds.Offset(-widget->GetRestoredBounds().x() +
370                          point_in_screen.x() -
371                          mouse_offset_.x(), 0);
372        widget->SetVisibilityChangedAnimationsEnabled(false);
373        widget->Restore();
374        widget->SetBounds(new_bounds);
375        AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width,
376                                         point_in_screen,
377                                         &drag_bounds);
378        widget->SetVisibilityChangedAnimationsEnabled(true);
379      }
380      RunMoveLoop(GetWindowOffset(point_in_screen));
381      return;
382    }
383  }
384
385  ContinueDragging(point_in_screen);
386}
387
388void TabDragController::EndDrag(EndDragReason reason) {
389  TRACE_EVENT0("views", "TabDragController::EndDrag");
390
391  // If we're dragging a window ignore capture lost since it'll ultimately
392  // trigger the move loop to end and we'll revert the drag when RunMoveLoop()
393  // finishes.
394  if (reason == END_DRAG_CAPTURE_LOST && is_dragging_window_)
395    return;
396  EndDragImpl(reason != END_DRAG_COMPLETE && source_tabstrip_ ?
397              CANCELED : NORMAL);
398}
399
400void TabDragController::InitTabDragData(Tab* tab,
401                                        TabDragData* drag_data) {
402  TRACE_EVENT0("views", "TabDragController::InitTabDragData");
403  drag_data->source_model_index =
404      source_tabstrip_->GetModelIndexOfTab(tab);
405  drag_data->contents = GetModel(source_tabstrip_)->GetWebContentsAt(
406      drag_data->source_model_index);
407  drag_data->pinned = source_tabstrip_->IsTabPinned(tab);
408  registrar_.Add(
409      this,
410      content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
411      content::Source<WebContents>(drag_data->contents));
412
413  if (!detach_into_browser_) {
414    drag_data->original_delegate = drag_data->contents->GetDelegate();
415    drag_data->contents->SetDelegate(this);
416  }
417}
418
419///////////////////////////////////////////////////////////////////////////////
420// TabDragController, PageNavigator implementation:
421
422WebContents* TabDragController::OpenURLFromTab(
423    WebContents* source,
424    const OpenURLParams& params) {
425  if (source_tab_drag_data()->original_delegate) {
426    OpenURLParams forward_params = params;
427    if (params.disposition == CURRENT_TAB)
428      forward_params.disposition = NEW_WINDOW;
429
430    return source_tab_drag_data()->original_delegate->OpenURLFromTab(
431        source, forward_params);
432  }
433  return NULL;
434}
435
436///////////////////////////////////////////////////////////////////////////////
437// TabDragController, content::WebContentsDelegate implementation:
438
439void TabDragController::NavigationStateChanged(const WebContents* source,
440                                               unsigned changed_flags) {
441  if (attached_tabstrip_ ||
442      changed_flags == content::INVALIDATE_TYPE_PAGE_ACTIONS) {
443    for (size_t i = 0; i < drag_data_.size(); ++i) {
444      if (drag_data_[i].contents == source) {
445        // Pass the NavigationStateChanged call to the original delegate so
446        // that the title is updated. Do this only when we are attached as
447        // otherwise the Tab isn't in the TabStrip (except for page action
448        // updates).
449        drag_data_[i].original_delegate->NavigationStateChanged(source,
450                                                                changed_flags);
451        break;
452      }
453    }
454  }
455}
456
457void TabDragController::AddNewContents(WebContents* source,
458                                       WebContents* new_contents,
459                                       WindowOpenDisposition disposition,
460                                       const gfx::Rect& initial_pos,
461                                       bool user_gesture,
462                                       bool* was_blocked) {
463  DCHECK_NE(CURRENT_TAB, disposition);
464
465  // Theoretically could be called while dragging if the page tries to
466  // spawn a window. Route this message back to the browser in most cases.
467  if (source_tab_drag_data()->original_delegate) {
468    source_tab_drag_data()->original_delegate->AddNewContents(
469        source, new_contents, disposition, initial_pos, user_gesture,
470        was_blocked);
471  }
472}
473
474bool TabDragController::ShouldSuppressDialogs() {
475  // When a dialog is about to be shown we revert the drag. Otherwise a modal
476  // dialog might appear and attempt to parent itself to a hidden tabcontents.
477  EndDragImpl(CANCELED);
478  return false;
479}
480
481content::JavaScriptDialogManager*
482TabDragController::GetJavaScriptDialogManager() {
483  return GetJavaScriptDialogManagerInstance();
484}
485
486void TabDragController::RequestMediaAccessPermission(
487    content::WebContents* web_contents,
488    const content::MediaStreamRequest& request,
489    const content::MediaResponseCallback& callback) {
490  ::RequestMediaAccessPermission(
491      web_contents,
492      Profile::FromBrowserContext(web_contents->GetBrowserContext()),
493      request,
494      callback);
495}
496
497///////////////////////////////////////////////////////////////////////////////
498// TabDragController, content::NotificationObserver implementation:
499
500void TabDragController::Observe(
501    int type,
502    const content::NotificationSource& source,
503    const content::NotificationDetails& details) {
504  DCHECK_EQ(content::NOTIFICATION_WEB_CONTENTS_DESTROYED, type);
505  WebContents* destroyed_web_contents =
506      content::Source<WebContents>(source).ptr();
507  for (size_t i = 0; i < drag_data_.size(); ++i) {
508    if (drag_data_[i].contents == destroyed_web_contents) {
509      // One of the tabs we're dragging has been destroyed. Cancel the drag.
510      if (destroyed_web_contents->GetDelegate() == this)
511        destroyed_web_contents->SetDelegate(NULL);
512      drag_data_[i].contents = NULL;
513      drag_data_[i].original_delegate = NULL;
514      EndDragImpl(TAB_DESTROYED);
515      return;
516    }
517  }
518  // If we get here it means we got notification for a tab we don't know about.
519  NOTREACHED();
520}
521
522void TabDragController::OnWidgetBoundsChanged(views::Widget* widget,
523                                              const gfx::Rect& new_bounds) {
524  TRACE_EVENT1("views", "TabDragController::OnWidgetBoundsChanged",
525               "new_bounds", new_bounds.ToString());
526
527  Drag(GetCursorScreenPoint());
528}
529
530void TabDragController::TabStripEmpty() {
531  DCHECK(detach_into_browser_);
532  GetModel(source_tabstrip_)->RemoveObserver(this);
533  // NULL out source_tabstrip_ so that we don't attempt to add back to it (in
534  // the case of a revert).
535  source_tabstrip_ = NULL;
536}
537
538///////////////////////////////////////////////////////////////////////////////
539// TabDragController, private:
540
541void TabDragController::InitWindowCreatePoint() {
542  // window_create_point_ is only used in CompleteDrag() (through
543  // GetWindowCreatePoint() to get the start point of the docked window) when
544  // the attached_tabstrip_ is NULL and all the window's related bound
545  // information are obtained from source_tabstrip_. So, we need to get the
546  // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise,
547  // the window_create_point_ is not in the correct coordinate system. Please
548  // refer to http://crbug.com/6223 comment #15 for detailed information.
549  views::View* first_tab = source_tabstrip_->tab_at(0);
550  views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_);
551  window_create_point_ = first_source_tab_point_;
552  window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y());
553}
554
555gfx::Point TabDragController::GetWindowCreatePoint(
556    const gfx::Point& origin) const {
557  // If the cursor is outside the monitor area, move it inside. For example,
558  // dropping a tab onto the task bar on Windows produces this situation.
559  gfx::Rect work_area = screen_->GetDisplayNearestPoint(origin).work_area();
560  gfx::Point create_point(origin);
561  if (!work_area.IsEmpty()) {
562    if (create_point.x() < work_area.x())
563      create_point.set_x(work_area.x());
564    else if (create_point.x() > work_area.right())
565      create_point.set_x(work_area.right());
566    if (create_point.y() < work_area.y())
567      create_point.set_y(work_area.y());
568    else if (create_point.y() > work_area.bottom())
569      create_point.set_y(work_area.bottom());
570  }
571  return gfx::Point(create_point.x() - window_create_point_.x(),
572                    create_point.y() - window_create_point_.y());
573}
574
575void TabDragController::SaveFocus() {
576  DCHECK(source_tabstrip_);
577  views::View* focused_view =
578      source_tabstrip_->GetFocusManager()->GetFocusedView();
579  if (focused_view)
580    views::ViewStorage::GetInstance()->StoreView(old_focused_view_id_,
581                                                 focused_view);
582  source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_);
583  // WARNING: we may have been deleted.
584}
585
586void TabDragController::RestoreFocus() {
587  if (attached_tabstrip_ != source_tabstrip_) {
588    if (is_dragging_new_browser_) {
589      content::WebContents* active_contents = source_dragged_contents();
590      if (active_contents && !active_contents->FocusLocationBarByDefault())
591        active_contents->Focus();
592    }
593    return;
594  }
595  views::View* old_focused_view =
596      views::ViewStorage::GetInstance()->RetrieveView(old_focused_view_id_);
597  if (!old_focused_view)
598    return;
599  old_focused_view->GetFocusManager()->SetFocusedView(old_focused_view);
600}
601
602bool TabDragController::CanStartDrag(const gfx::Point& point_in_screen) const {
603  // Determine if the mouse has moved beyond a minimum elasticity distance in
604  // any direction from the starting point.
605  static const int kMinimumDragDistance = 10;
606  int x_offset = abs(point_in_screen.x() - start_point_in_screen_.x());
607  int y_offset = abs(point_in_screen.y() - start_point_in_screen_.y());
608  return sqrt(pow(static_cast<float>(x_offset), 2) +
609              pow(static_cast<float>(y_offset), 2)) > kMinimumDragDistance;
610}
611
612void TabDragController::ContinueDragging(const gfx::Point& point_in_screen) {
613  TRACE_EVENT1("views", "TabDragController::ContinueDragging",
614               "point_in_screen", point_in_screen.ToString());
615
616  DCHECK(!detach_into_browser_ || attached_tabstrip_);
617
618  TabStrip* target_tabstrip = detach_behavior_ == DETACHABLE ?
619      GetTargetTabStripForPoint(point_in_screen) : source_tabstrip_;
620  bool tab_strip_changed = (target_tabstrip != attached_tabstrip_);
621
622  if (attached_tabstrip_) {
623    int move_delta = point_in_screen.x() - last_point_in_screen_.x();
624    if (move_delta > 0)
625      mouse_move_direction_ |= kMovedMouseRight;
626    else if (move_delta < 0)
627      mouse_move_direction_ |= kMovedMouseLeft;
628  }
629  last_point_in_screen_ = point_in_screen;
630
631  if (tab_strip_changed) {
632    is_dragging_new_browser_ = false;
633    did_restore_window_ = false;
634    if (detach_into_browser_ &&
635        DragBrowserToNewTabStrip(target_tabstrip, point_in_screen) ==
636        DRAG_BROWSER_RESULT_STOP) {
637      return;
638    } else if (!detach_into_browser_) {
639      if (attached_tabstrip_)
640        Detach(RELEASE_CAPTURE);
641      if (target_tabstrip)
642        Attach(target_tabstrip, point_in_screen);
643    }
644  }
645  if (is_dragging_window_) {
646    static_cast<base::Timer*>(&bring_to_front_timer_)->Start(FROM_HERE,
647        base::TimeDelta::FromMilliseconds(kBringToFrontDelay),
648        base::Bind(&TabDragController::BringWindowUnderPointToFront,
649                   base::Unretained(this), point_in_screen));
650  }
651
652  if (!is_dragging_window_ && attached_tabstrip_) {
653    if (move_only()) {
654      DragActiveTabStacked(point_in_screen);
655    } else {
656      MoveAttached(point_in_screen);
657      if (tab_strip_changed) {
658        // Move the corresponding window to the front. We do this after the
659        // move as on windows activate triggers a synchronous paint.
660        attached_tabstrip_->GetWidget()->Activate();
661      }
662    }
663  }
664}
665
666TabDragController::DragBrowserResultType
667TabDragController::DragBrowserToNewTabStrip(
668    TabStrip* target_tabstrip,
669    const gfx::Point& point_in_screen) {
670  TRACE_EVENT1("views", "TabDragController::DragBrowserToNewTabStrip",
671               "point_in_screen", point_in_screen.ToString());
672
673  if (!target_tabstrip) {
674    DetachIntoNewBrowserAndRunMoveLoop(point_in_screen);
675    return DRAG_BROWSER_RESULT_STOP;
676  }
677  if (is_dragging_window_) {
678    // ReleaseCapture() is going to result in calling back to us (because it
679    // results in a move). That'll cause all sorts of problems.  Reset the
680    // observer so we don't get notified and process the event.
681    if (use_aura_capture_policy_) {
682      move_loop_widget_->RemoveObserver(this);
683      move_loop_widget_ = NULL;
684    }
685    views::Widget* browser_widget = GetAttachedBrowserWidget();
686    // Need to release the drag controller before starting the move loop as it's
687    // going to trigger capture lost, which cancels drag.
688    attached_tabstrip_->ReleaseDragController();
689    target_tabstrip->OwnDragController(this);
690    // Disable animations so that we don't see a close animation on aero.
691    browser_widget->SetVisibilityChangedAnimationsEnabled(false);
692    // For aura we can't release capture, otherwise it'll cancel a gesture.
693    // Instead we have to directly change capture.
694    if (use_aura_capture_policy_)
695      target_tabstrip->GetWidget()->SetCapture(attached_tabstrip_);
696    else
697      browser_widget->ReleaseCapture();
698#if defined(OS_WIN)
699    // The Gesture recognizer does not work well currently when capture changes
700    // while a touch gesture is in progress. So we need to manually transfer
701    // gesture sequence and the GR's touch events queue to the new window. This
702    // should really be done somewhere in capture change code and or inside the
703    // GR. But we currently do not have a consistent way for doing it that would
704    // work in all cases. Hence this hack.
705    ui::GestureRecognizer::Get()->TransferEventsTo(
706        browser_widget->GetNativeView(),
707        target_tabstrip->GetWidget()->GetNativeView());
708#endif
709
710    // The window is going away. Since the drag is still on going we don't want
711    // that to effect the position of any windows.
712    SetWindowPositionManaged(browser_widget->GetNativeView(), false);
713
714#if !defined(OS_LINUX) || defined(OS_CHROMEOS)
715    // EndMoveLoop is going to snap the window back to its original location.
716    // Hide it so users don't see this. Hiding a window in Linux aura causes
717    // it to lose capture so skip it.
718    browser_widget->Hide();
719#endif
720    browser_widget->EndMoveLoop();
721
722    // Ideally we would always swap the tabs now, but on non-ash it seems that
723    // running the move loop implicitly activates the window when done, leading
724    // to all sorts of flicker. So, on non-ash, instead we process the move
725    // after the loop completes. But on chromeos, we can do tab swapping now to
726    // avoid the tab flashing issue(crbug.com/116329).
727    if (use_aura_capture_policy_) {
728      is_dragging_window_ = false;
729      Detach(DONT_RELEASE_CAPTURE);
730      Attach(target_tabstrip, point_in_screen);
731      // Move the tabs into position.
732      MoveAttached(point_in_screen);
733      attached_tabstrip_->GetWidget()->Activate();
734    } else {
735      tab_strip_to_attach_to_after_exit_ = target_tabstrip;
736    }
737
738    waiting_for_run_loop_to_exit_ = true;
739    end_run_loop_behavior_ = END_RUN_LOOP_CONTINUE_DRAGGING;
740    return DRAG_BROWSER_RESULT_STOP;
741  }
742  Detach(DONT_RELEASE_CAPTURE);
743  Attach(target_tabstrip, point_in_screen);
744  return DRAG_BROWSER_RESULT_CONTINUE;
745}
746
747void TabDragController::DragActiveTabStacked(
748    const gfx::Point& point_in_screen) {
749  if (attached_tabstrip_->tab_count() !=
750      static_cast<int>(initial_tab_positions_.size()))
751    return;  // TODO: should cancel drag if this happens.
752
753  int delta = point_in_screen.x() - start_point_in_screen_.x();
754  attached_tabstrip_->DragActiveTab(initial_tab_positions_, delta);
755}
756
757void TabDragController::MoveAttachedToNextStackedIndex(
758    const gfx::Point& point_in_screen) {
759  int index = attached_tabstrip_->touch_layout_->active_index();
760  if (index + 1 >= attached_tabstrip_->tab_count())
761    return;
762
763  GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index + 1);
764  StartMoveStackedTimerIfNecessary(point_in_screen,
765                                   kMoveAttachedSubsequentDelay);
766}
767
768void TabDragController::MoveAttachedToPreviousStackedIndex(
769    const gfx::Point& point_in_screen) {
770  int index = attached_tabstrip_->touch_layout_->active_index();
771  if (index <= attached_tabstrip_->GetMiniTabCount())
772    return;
773
774  GetModel(attached_tabstrip_)->MoveSelectedTabsTo(index - 1);
775  StartMoveStackedTimerIfNecessary(point_in_screen,
776                                   kMoveAttachedSubsequentDelay);
777}
778
779void TabDragController::MoveAttached(const gfx::Point& point_in_screen) {
780  DCHECK(attached_tabstrip_);
781  DCHECK(!is_dragging_window_);
782
783  gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);
784
785  // Determine the horizontal move threshold. This is dependent on the width
786  // of tabs. The smaller the tabs compared to the standard size, the smaller
787  // the threshold.
788  int threshold = kHorizontalMoveThreshold;
789  if (!attached_tabstrip_->touch_layout_.get()) {
790    double unselected, selected;
791    attached_tabstrip_->GetCurrentTabWidths(&unselected, &selected);
792    double ratio = unselected / Tab::GetStandardSize().width();
793    threshold = static_cast<int>(ratio * kHorizontalMoveThreshold);
794  }
795  // else case: touch tabs never shrink.
796
797  std::vector<Tab*> tabs(drag_data_.size());
798  for (size_t i = 0; i < drag_data_.size(); ++i)
799    tabs[i] = drag_data_[i].attached_tab;
800
801  bool did_layout = false;
802  // Update the model, moving the WebContents from one index to another. Do this
803  // only if we have moved a minimum distance since the last reorder (to prevent
804  // jitter) or if this the first move and the tabs are not consecutive.
805  if ((abs(point_in_screen.x() - last_move_screen_loc_) > threshold ||
806        (initial_move_ && !AreTabsConsecutive()))) {
807    TabStripModel* attached_model = GetModel(attached_tabstrip_);
808    gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
809    int to_index = GetInsertionIndexForDraggedBounds(bounds);
810    bool do_move = true;
811    // While dragging within a tabstrip the expectation is the insertion index
812    // is based on the left edge of the tabs being dragged. OTOH when dragging
813    // into a new tabstrip (attaching) the expectation is the insertion index is
814    // based on the cursor. This proves problematic as insertion may change the
815    // size of the tabs, resulting in the index calculated before the insert
816    // differing from the index calculated after the insert. To alleviate this
817    // the index is chosen before insertion, and subsequently a new index is
818    // only used once the mouse moves enough such that the index changes based
819    // on the direction the mouse moved relative to |attach_x_| (smaller
820    // x-coordinate should yield a smaller index or larger x-coordinate yields a
821    // larger index).
822    if (attach_index_ != -1) {
823      gfx::Point tab_strip_point(point_in_screen);
824      views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point);
825      const int new_x =
826          attached_tabstrip_->GetMirroredXInView(tab_strip_point.x());
827      if (new_x < attach_x_)
828        to_index = std::min(to_index, attach_index_);
829      else
830        to_index = std::max(to_index, attach_index_);
831      if (to_index != attach_index_)
832        attach_index_ = -1;  // Once a valid move is detected, don't constrain.
833      else
834        do_move = false;
835    }
836    if (do_move) {
837      WebContents* last_contents = drag_data_[drag_data_.size() - 1].contents;
838      int index_of_last_item =
839          attached_model->GetIndexOfWebContents(last_contents);
840      if (initial_move_) {
841        // TabStrip determines if the tabs needs to be animated based on model
842        // position. This means we need to invoke LayoutDraggedTabsAt before
843        // changing the model.
844        attached_tabstrip_->LayoutDraggedTabsAt(
845            tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
846            initial_move_);
847        did_layout = true;
848      }
849      attached_model->MoveSelectedTabsTo(to_index);
850
851      // Move may do nothing in certain situations (such as when dragging pinned
852      // tabs). Make sure the tabstrip actually changed before updating
853      // last_move_screen_loc_.
854      if (index_of_last_item !=
855          attached_model->GetIndexOfWebContents(last_contents)) {
856        last_move_screen_loc_ = point_in_screen.x();
857      }
858    }
859  }
860
861  if (!did_layout) {
862    attached_tabstrip_->LayoutDraggedTabsAt(
863        tabs, source_tab_drag_data()->attached_tab, dragged_view_point,
864        initial_move_);
865  }
866
867  StartMoveStackedTimerIfNecessary(point_in_screen, kMoveAttachedInitialDelay);
868
869  initial_move_ = false;
870}
871
872void TabDragController::StartMoveStackedTimerIfNecessary(
873    const gfx::Point& point_in_screen,
874    int delay_ms) {
875  DCHECK(attached_tabstrip_);
876
877  StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get();
878  if (!touch_layout)
879    return;
880
881  gfx::Point dragged_view_point = GetAttachedDragPoint(point_in_screen);
882  gfx::Rect bounds = GetDraggedViewTabStripBounds(dragged_view_point);
883  int index = touch_layout->active_index();
884  if (ShouldDragToNextStackedTab(bounds, index)) {
885    static_cast<base::Timer*>(&move_stacked_timer_)->Start(
886        FROM_HERE,
887        base::TimeDelta::FromMilliseconds(delay_ms),
888        base::Bind(&TabDragController::MoveAttachedToNextStackedIndex,
889                   base::Unretained(this), point_in_screen));
890  } else if (ShouldDragToPreviousStackedTab(bounds, index)) {
891    static_cast<base::Timer*>(&move_stacked_timer_)->Start(
892        FROM_HERE,
893        base::TimeDelta::FromMilliseconds(delay_ms),
894        base::Bind(&TabDragController::MoveAttachedToPreviousStackedIndex,
895                   base::Unretained(this), point_in_screen));
896  }
897}
898
899TabDragController::DetachPosition TabDragController::GetDetachPosition(
900    const gfx::Point& point_in_screen) {
901  DCHECK(attached_tabstrip_);
902  gfx::Point attached_point(point_in_screen);
903  views::View::ConvertPointFromScreen(attached_tabstrip_, &attached_point);
904  if (attached_point.x() < 0)
905    return DETACH_BEFORE;
906  if (attached_point.x() >= attached_tabstrip_->width())
907    return DETACH_AFTER;
908  return DETACH_ABOVE_OR_BELOW;
909}
910
911TabStrip* TabDragController::GetTargetTabStripForPoint(
912    const gfx::Point& point_in_screen) {
913  TRACE_EVENT1("views", "TabDragController::GetTargetTabStripForPoint",
914               "point_in_screen", point_in_screen.ToString());
915
916  if (move_only() && attached_tabstrip_) {
917    DCHECK_EQ(DETACHABLE, detach_behavior_);
918    // move_only() is intended for touch, in which case we only want to detach
919    // if the touch point moves significantly in the vertical distance.
920    gfx::Rect tabstrip_bounds = GetViewScreenBounds(attached_tabstrip_);
921    if (DoesRectContainVerticalPointExpanded(tabstrip_bounds,
922                                             kTouchVerticalDetachMagnetism,
923                                             point_in_screen.y()))
924      return attached_tabstrip_;
925  }
926  gfx::NativeWindow local_window =
927      GetLocalProcessWindow(point_in_screen, is_dragging_window_);
928  // Do not allow dragging into a window with a modal dialog, it causes a weird
929  // behavior.  See crbug.com/336691
930  if (!wm::GetModalTransient(local_window)) {
931    TabStrip* tab_strip = GetTabStripForWindow(local_window);
932    if (tab_strip && DoesTabStripContain(tab_strip, point_in_screen))
933      return tab_strip;
934  }
935
936  return is_dragging_window_ ? attached_tabstrip_ : NULL;
937}
938
939TabStrip* TabDragController::GetTabStripForWindow(gfx::NativeWindow window) {
940  if (!window)
941    return NULL;
942  BrowserView* browser_view =
943      BrowserView::GetBrowserViewForNativeWindow(window);
944  // We don't allow drops on windows that don't have tabstrips.
945  if (!browser_view ||
946      !browser_view->browser()->SupportsWindowFeature(
947          Browser::FEATURE_TABSTRIP))
948    return NULL;
949
950  TabStrip* other_tabstrip = browser_view->tabstrip();
951  TabStrip* tab_strip =
952      attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_;
953  DCHECK(tab_strip);
954
955  return other_tabstrip->controller()->IsCompatibleWith(tab_strip) ?
956      other_tabstrip : NULL;
957}
958
959bool TabDragController::DoesTabStripContain(
960    TabStrip* tabstrip,
961    const gfx::Point& point_in_screen) const {
962  // Make sure the specified screen point is actually within the bounds of the
963  // specified tabstrip...
964  gfx::Rect tabstrip_bounds = GetViewScreenBounds(tabstrip);
965  return point_in_screen.x() < tabstrip_bounds.right() &&
966      point_in_screen.x() >= tabstrip_bounds.x() &&
967      DoesRectContainVerticalPointExpanded(tabstrip_bounds,
968                                           kVerticalDetachMagnetism,
969                                           point_in_screen.y());
970}
971
972void TabDragController::Attach(TabStrip* attached_tabstrip,
973                               const gfx::Point& point_in_screen) {
974  TRACE_EVENT1("views", "TabDragController::Attach",
975               "point_in_screen", point_in_screen.ToString());
976
977  DCHECK(!attached_tabstrip_);  // We should already have detached by the time
978                                // we get here.
979
980  attached_tabstrip_ = attached_tabstrip;
981
982  std::vector<Tab*> tabs =
983      GetTabsMatchingDraggedContents(attached_tabstrip_);
984
985  if (tabs.empty()) {
986    // Transitioning from detached to attached to a new tabstrip. Add tabs to
987    // the new model.
988
989    selection_model_before_attach_.Copy(attached_tabstrip->GetSelectionModel());
990
991    if (!detach_into_browser_) {
992      // Remove ourselves as the delegate now that the dragged WebContents is
993      // being inserted back into a Browser.
994      for (size_t i = 0; i < drag_data_.size(); ++i) {
995        drag_data_[i].contents->SetDelegate(NULL);
996        drag_data_[i].original_delegate = NULL;
997      }
998
999      // Return the WebContents to normalcy.
1000      source_dragged_contents()->DecrementCapturerCount();
1001    }
1002
1003    // Inserting counts as a move. We don't want the tabs to jitter when the
1004    // user moves the tab immediately after attaching it.
1005    last_move_screen_loc_ = point_in_screen.x();
1006
1007    // Figure out where to insert the tab based on the bounds of the dragged
1008    // representation and the ideal bounds of the other Tabs already in the
1009    // strip. ("ideal bounds" are stable even if the Tabs' actual bounds are
1010    // changing due to animation).
1011    gfx::Point tab_strip_point(point_in_screen);
1012    views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_strip_point);
1013    tab_strip_point.set_x(
1014        attached_tabstrip_->GetMirroredXInView(tab_strip_point.x()));
1015    tab_strip_point.Offset(0, -mouse_offset_.y());
1016    gfx::Rect bounds = GetDraggedViewTabStripBounds(tab_strip_point);
1017    int index = GetInsertionIndexForDraggedBounds(bounds);
1018    attach_index_ = index;
1019    attach_x_ = tab_strip_point.x();
1020    base::AutoReset<bool> setter(&is_mutating_, true);
1021    for (size_t i = 0; i < drag_data_.size(); ++i) {
1022      int add_types = TabStripModel::ADD_NONE;
1023      if (attached_tabstrip_->touch_layout_.get()) {
1024        // StackedTabStripLayout positions relative to the active tab, if we
1025        // don't add the tab as active things bounce around.
1026        DCHECK_EQ(1u, drag_data_.size());
1027        add_types |= TabStripModel::ADD_ACTIVE;
1028      }
1029      if (drag_data_[i].pinned)
1030        add_types |= TabStripModel::ADD_PINNED;
1031      GetModel(attached_tabstrip_)->InsertWebContentsAt(
1032          index + i, drag_data_[i].contents, add_types);
1033    }
1034
1035    tabs = GetTabsMatchingDraggedContents(attached_tabstrip_);
1036  }
1037  DCHECK_EQ(tabs.size(), drag_data_.size());
1038  for (size_t i = 0; i < drag_data_.size(); ++i)
1039    drag_data_[i].attached_tab = tabs[i];
1040
1041  attached_tabstrip_->StartedDraggingTabs(tabs);
1042
1043  ResetSelection(GetModel(attached_tabstrip_));
1044
1045  // The size of the dragged tab may have changed. Adjust the x offset so that
1046  // ratio of mouse_offset_ to original width is maintained.
1047  std::vector<Tab*> tabs_to_source(tabs);
1048  tabs_to_source.erase(tabs_to_source.begin() + source_tab_index_ + 1,
1049                       tabs_to_source.end());
1050  int new_x = attached_tabstrip_->GetSizeNeededForTabs(tabs_to_source) -
1051      tabs[source_tab_index_]->width() +
1052      static_cast<int>(offset_to_width_ratio_ *
1053                       tabs[source_tab_index_]->width());
1054  mouse_offset_.set_x(new_x);
1055
1056  // Transfer ownership of us to the new tabstrip as well as making sure the
1057  // window has capture. This is important so that if activation changes the
1058  // drag isn't prematurely canceled.
1059  if (detach_into_browser_) {
1060    attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_);
1061    attached_tabstrip_->OwnDragController(this);
1062  }
1063
1064  // Redirect all mouse events to the TabStrip so that the tab that originated
1065  // the drag can safely be deleted.
1066  if (detach_into_browser_ || attached_tabstrip_ == source_tabstrip_) {
1067    static_cast<views::internal::RootView*>(
1068        attached_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler(
1069            attached_tabstrip_);
1070  }
1071}
1072
1073void TabDragController::Detach(ReleaseCapture release_capture) {
1074  TRACE_EVENT1("views", "TabDragController::Detach",
1075               "release_capture", release_capture);
1076
1077  attach_index_ = -1;
1078
1079  // When the user detaches we assume they want to reorder.
1080  move_behavior_ = REORDER;
1081
1082  // Release ownership of the drag controller and mouse capture. When we
1083  // reattach ownership is transfered.
1084  if (detach_into_browser_) {
1085    attached_tabstrip_->ReleaseDragController();
1086    if (release_capture == RELEASE_CAPTURE)
1087      attached_tabstrip_->GetWidget()->ReleaseCapture();
1088  }
1089
1090  mouse_move_direction_ = kMovedMouseLeft | kMovedMouseRight;
1091
1092  // Prevent the WebContents HWND from being hidden by any of the model
1093  // operations performed during the drag.
1094  if (!detach_into_browser_)
1095    source_dragged_contents()->IncrementCapturerCount(gfx::Size());
1096
1097  std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs();
1098  TabStripModel* attached_model = GetModel(attached_tabstrip_);
1099  std::vector<TabRendererData> tab_data;
1100  for (size_t i = 0; i < drag_data_.size(); ++i) {
1101    tab_data.push_back(drag_data_[i].attached_tab->data());
1102    int index = attached_model->GetIndexOfWebContents(drag_data_[i].contents);
1103    DCHECK_NE(-1, index);
1104
1105    // Hide the tab so that the user doesn't see it animate closed.
1106    drag_data_[i].attached_tab->SetVisible(false);
1107
1108    attached_model->DetachWebContentsAt(index);
1109
1110    // Detaching resets the delegate, but we still want to be the delegate.
1111    if (!detach_into_browser_)
1112      drag_data_[i].contents->SetDelegate(this);
1113
1114    // Detaching may end up deleting the tab, drop references to it.
1115    drag_data_[i].attached_tab = NULL;
1116  }
1117
1118  // If we've removed the last Tab from the TabStrip, hide the frame now.
1119  if (!attached_model->empty()) {
1120    if (!selection_model_before_attach_.empty() &&
1121        selection_model_before_attach_.active() >= 0 &&
1122        selection_model_before_attach_.active() < attached_model->count()) {
1123      // Restore the selection.
1124      attached_model->SetSelectionFromModel(selection_model_before_attach_);
1125    } else if (attached_tabstrip_ == source_tabstrip_ &&
1126               !initial_selection_model_.empty()) {
1127      RestoreInitialSelection();
1128    }
1129  }
1130
1131  attached_tabstrip_->DraggedTabsDetached();
1132  attached_tabstrip_ = NULL;
1133}
1134
1135void TabDragController::DetachIntoNewBrowserAndRunMoveLoop(
1136    const gfx::Point& point_in_screen) {
1137  if (GetModel(attached_tabstrip_)->count() ==
1138      static_cast<int>(drag_data_.size())) {
1139    // All the tabs in a browser are being dragged but all the tabs weren't
1140    // initially being dragged. For this to happen the user would have to
1141    // start dragging a set of tabs, the other tabs close, then detach.
1142    RunMoveLoop(GetWindowOffset(point_in_screen));
1143    return;
1144  }
1145
1146  const int last_tabstrip_width = attached_tabstrip_->tab_area_width();
1147  std::vector<gfx::Rect> drag_bounds = CalculateBoundsForDraggedTabs();
1148  OffsetX(GetAttachedDragPoint(point_in_screen).x(), &drag_bounds);
1149
1150  gfx::Vector2d drag_offset;
1151  Browser* browser = CreateBrowserForDrag(
1152      attached_tabstrip_, point_in_screen, &drag_offset, &drag_bounds);
1153#if defined(OS_WIN)
1154  gfx::NativeView attached_native_view =
1155    attached_tabstrip_->GetWidget()->GetNativeView();
1156#endif
1157  Detach(use_aura_capture_policy_ ? DONT_RELEASE_CAPTURE : RELEASE_CAPTURE);
1158  BrowserView* dragged_browser_view =
1159      BrowserView::GetBrowserViewForBrowser(browser);
1160  views::Widget* dragged_widget = dragged_browser_view->GetWidget();
1161#if defined(OS_WIN)
1162    // The Gesture recognizer does not work well currently when capture changes
1163    // while a touch gesture is in progress. So we need to manually transfer
1164    // gesture sequence and the GR's touch events queue to the new window. This
1165    // should really be done somewhere in capture change code and or inside the
1166    // GR. But we currently do not have a consistent way for doing it that would
1167    // work in all cases. Hence this hack.
1168    ui::GestureRecognizer::Get()->TransferEventsTo(
1169        attached_native_view,
1170        dragged_widget->GetNativeView());
1171#endif
1172  dragged_widget->SetVisibilityChangedAnimationsEnabled(false);
1173  Attach(dragged_browser_view->tabstrip(), gfx::Point());
1174  AdjustBrowserAndTabBoundsForDrag(last_tabstrip_width,
1175                                   point_in_screen,
1176                                   &drag_bounds);
1177  WindowPositionManagedUpdater updater;
1178  dragged_widget->AddObserver(&updater);
1179  browser->window()->Show();
1180  dragged_widget->RemoveObserver(&updater);
1181  dragged_widget->SetVisibilityChangedAnimationsEnabled(true);
1182  // Activate may trigger a focus loss, destroying us.
1183  {
1184    base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
1185    browser->window()->Activate();
1186    if (!ref)
1187      return;
1188  }
1189  RunMoveLoop(drag_offset);
1190}
1191
1192void TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) {
1193  // If the user drags the whole window we'll assume they are going to attach to
1194  // another window and therefore want to reorder.
1195  move_behavior_ = REORDER;
1196
1197  move_loop_widget_ = GetAttachedBrowserWidget();
1198  DCHECK(move_loop_widget_);
1199  move_loop_widget_->AddObserver(this);
1200  is_dragging_window_ = true;
1201  base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
1202  // Running the move loop releases mouse capture on non-ash, which triggers
1203  // destroying the drag loop. Release mouse capture ourself before this while
1204  // the DragController isn't owned by the TabStrip.
1205  if (host_desktop_type_ != chrome::HOST_DESKTOP_TYPE_ASH) {
1206    attached_tabstrip_->ReleaseDragController();
1207    attached_tabstrip_->GetWidget()->ReleaseCapture();
1208    attached_tabstrip_->OwnDragController(this);
1209  }
1210  const views::Widget::MoveLoopSource move_loop_source =
1211      event_source_ == EVENT_SOURCE_MOUSE ?
1212      views::Widget::MOVE_LOOP_SOURCE_MOUSE :
1213      views::Widget::MOVE_LOOP_SOURCE_TOUCH;
1214  const views::Widget::MoveLoopEscapeBehavior escape_behavior =
1215      is_dragging_new_browser_ ?
1216          views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_HIDE :
1217          views::Widget::MOVE_LOOP_ESCAPE_BEHAVIOR_DONT_HIDE;
1218  views::Widget::MoveLoopResult result =
1219      move_loop_widget_->RunMoveLoop(
1220          drag_offset, move_loop_source, escape_behavior);
1221  content::NotificationService::current()->Notify(
1222      chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE,
1223      content::NotificationService::AllBrowserContextsAndSources(),
1224      content::NotificationService::NoDetails());
1225
1226  if (!ref)
1227    return;
1228  // Under chromeos we immediately set the |move_loop_widget_| to NULL.
1229  if (move_loop_widget_) {
1230    move_loop_widget_->RemoveObserver(this);
1231    move_loop_widget_ = NULL;
1232  }
1233  is_dragging_window_ = false;
1234  waiting_for_run_loop_to_exit_ = false;
1235  if (end_run_loop_behavior_ == END_RUN_LOOP_CONTINUE_DRAGGING) {
1236    end_run_loop_behavior_ = END_RUN_LOOP_STOP_DRAGGING;
1237    if (tab_strip_to_attach_to_after_exit_) {
1238      gfx::Point point_in_screen(GetCursorScreenPoint());
1239      Detach(DONT_RELEASE_CAPTURE);
1240      Attach(tab_strip_to_attach_to_after_exit_, point_in_screen);
1241      // Move the tabs into position.
1242      MoveAttached(point_in_screen);
1243      attached_tabstrip_->GetWidget()->Activate();
1244      // Activate may trigger a focus loss, destroying us.
1245      if (!ref)
1246        return;
1247      tab_strip_to_attach_to_after_exit_ = NULL;
1248    }
1249    DCHECK(attached_tabstrip_);
1250    attached_tabstrip_->GetWidget()->SetCapture(attached_tabstrip_);
1251  } else if (active_) {
1252    EndDrag(result == views::Widget::MOVE_LOOP_CANCELED ?
1253            END_DRAG_CANCEL : END_DRAG_COMPLETE);
1254  }
1255}
1256
1257int TabDragController::GetInsertionIndexFrom(const gfx::Rect& dragged_bounds,
1258                                             int start,
1259                                             int delta) const {
1260  for (int i = start, tab_count = attached_tabstrip_->tab_count();
1261       i >= 0 && i < tab_count; i += delta) {
1262    const gfx::Rect& ideal_bounds = attached_tabstrip_->ideal_bounds(i);
1263    gfx::Rect left_half, right_half;
1264    ideal_bounds.SplitVertically(&left_half, &right_half);
1265    if (dragged_bounds.x() >= right_half.x() &&
1266        dragged_bounds.x() < right_half.right()) {
1267      return i + 1;
1268    } else if (dragged_bounds.x() >= left_half.x() &&
1269               dragged_bounds.x() < left_half.right()) {
1270      return i;
1271    }
1272  }
1273  return -1;
1274}
1275
1276int TabDragController::GetInsertionIndexForDraggedBounds(
1277    const gfx::Rect& dragged_bounds) const {
1278  int index = -1;
1279  if (attached_tabstrip_->touch_layout_.get()) {
1280    index = GetInsertionIndexForDraggedBoundsStacked(dragged_bounds);
1281    if (index != -1) {
1282      // Only move the tab to the left/right if the user actually moved the
1283      // mouse that way. This is necessary as tabs with stacked tabs
1284      // before/after them have multiple drag positions.
1285      int active_index = attached_tabstrip_->touch_layout_->active_index();
1286      if ((index < active_index &&
1287           (mouse_move_direction_ & kMovedMouseLeft) == 0) ||
1288          (index > active_index &&
1289           (mouse_move_direction_ & kMovedMouseRight) == 0)) {
1290        index = active_index;
1291      }
1292    }
1293  } else {
1294    index = GetInsertionIndexFrom(dragged_bounds, 0, 1);
1295  }
1296  if (index == -1) {
1297    int tab_count = attached_tabstrip_->tab_count();
1298    int right_tab_x = tab_count == 0 ? 0 :
1299        attached_tabstrip_->ideal_bounds(tab_count - 1).right();
1300    if (dragged_bounds.right() > right_tab_x) {
1301      index = GetModel(attached_tabstrip_)->count();
1302    } else {
1303      index = 0;
1304    }
1305  }
1306
1307  if (!drag_data_[0].attached_tab) {
1308    // If 'attached_tab' is NULL, it means we're in the process of attaching and
1309    // don't need to constrain the index.
1310    return index;
1311  }
1312
1313  int max_index = GetModel(attached_tabstrip_)->count() -
1314      static_cast<int>(drag_data_.size());
1315  return std::max(0, std::min(max_index, index));
1316}
1317
1318bool TabDragController::ShouldDragToNextStackedTab(
1319    const gfx::Rect& dragged_bounds,
1320    int index) const {
1321  if (index + 1 >= attached_tabstrip_->tab_count() ||
1322      !attached_tabstrip_->touch_layout_->IsStacked(index + 1) ||
1323      (mouse_move_direction_ & kMovedMouseRight) == 0)
1324    return false;
1325
1326  int active_x = attached_tabstrip_->ideal_bounds(index).x();
1327  int next_x = attached_tabstrip_->ideal_bounds(index + 1).x();
1328  int mid_x = std::min(next_x - kStackedDistance,
1329                       active_x + (next_x - active_x) / 4);
1330  return dragged_bounds.x() >= mid_x;
1331}
1332
1333bool TabDragController::ShouldDragToPreviousStackedTab(
1334    const gfx::Rect& dragged_bounds,
1335    int index) const {
1336  if (index - 1 < attached_tabstrip_->GetMiniTabCount() ||
1337      !attached_tabstrip_->touch_layout_->IsStacked(index - 1) ||
1338      (mouse_move_direction_ & kMovedMouseLeft) == 0)
1339    return false;
1340
1341  int active_x = attached_tabstrip_->ideal_bounds(index).x();
1342  int previous_x = attached_tabstrip_->ideal_bounds(index - 1).x();
1343  int mid_x = std::max(previous_x + kStackedDistance,
1344                       active_x - (active_x - previous_x) / 4);
1345  return dragged_bounds.x() <= mid_x;
1346}
1347
1348int TabDragController::GetInsertionIndexForDraggedBoundsStacked(
1349    const gfx::Rect& dragged_bounds) const {
1350  StackedTabStripLayout* touch_layout = attached_tabstrip_->touch_layout_.get();
1351  int active_index = touch_layout->active_index();
1352  // Search from the active index to the front of the tabstrip. Do this as tabs
1353  // overlap each other from the active index.
1354  int index = GetInsertionIndexFrom(dragged_bounds, active_index, -1);
1355  if (index != active_index)
1356    return index;
1357  if (index == -1)
1358    return GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1);
1359
1360  // The position to drag to corresponds to the active tab. If the next/previous
1361  // tab is stacked, then shorten the distance used to determine insertion
1362  // bounds. We do this as GetInsertionIndexFrom() uses the bounds of the
1363  // tabs. When tabs are stacked the next/previous tab is on top of the tab.
1364  if (active_index + 1 < attached_tabstrip_->tab_count() &&
1365      touch_layout->IsStacked(active_index + 1)) {
1366    index = GetInsertionIndexFrom(dragged_bounds, active_index + 1, 1);
1367    if (index == -1 && ShouldDragToNextStackedTab(dragged_bounds, active_index))
1368      index = active_index + 1;
1369    else if (index == -1)
1370      index = active_index;
1371  } else if (ShouldDragToPreviousStackedTab(dragged_bounds, active_index)) {
1372    index = active_index - 1;
1373  }
1374  return index;
1375}
1376
1377gfx::Rect TabDragController::GetDraggedViewTabStripBounds(
1378    const gfx::Point& tab_strip_point) {
1379  // attached_tab is NULL when inserting into a new tabstrip.
1380  if (source_tab_drag_data()->attached_tab) {
1381    return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1382                     source_tab_drag_data()->attached_tab->width(),
1383                     source_tab_drag_data()->attached_tab->height());
1384  }
1385
1386  double sel_width, unselected_width;
1387  attached_tabstrip_->GetCurrentTabWidths(&sel_width, &unselected_width);
1388  return gfx::Rect(tab_strip_point.x(), tab_strip_point.y(),
1389                   static_cast<int>(sel_width),
1390                   Tab::GetStandardSize().height());
1391}
1392
1393gfx::Point TabDragController::GetAttachedDragPoint(
1394    const gfx::Point& point_in_screen) {
1395  DCHECK(attached_tabstrip_);  // The tab must be attached.
1396
1397  gfx::Point tab_loc(point_in_screen);
1398  views::View::ConvertPointFromScreen(attached_tabstrip_, &tab_loc);
1399  const int x =
1400      attached_tabstrip_->GetMirroredXInView(tab_loc.x()) - mouse_offset_.x();
1401
1402  // TODO: consider caching this.
1403  std::vector<Tab*> attached_tabs;
1404  for (size_t i = 0; i < drag_data_.size(); ++i)
1405    attached_tabs.push_back(drag_data_[i].attached_tab);
1406  const int size = attached_tabstrip_->GetSizeNeededForTabs(attached_tabs);
1407  const int max_x = attached_tabstrip_->width() - size;
1408  return gfx::Point(std::min(std::max(x, 0), max_x), 0);
1409}
1410
1411std::vector<Tab*> TabDragController::GetTabsMatchingDraggedContents(
1412    TabStrip* tabstrip) {
1413  TabStripModel* model = GetModel(attached_tabstrip_);
1414  std::vector<Tab*> tabs;
1415  for (size_t i = 0; i < drag_data_.size(); ++i) {
1416    int model_index = model->GetIndexOfWebContents(drag_data_[i].contents);
1417    if (model_index == TabStripModel::kNoTab)
1418      return std::vector<Tab*>();
1419    tabs.push_back(tabstrip->tab_at(model_index));
1420  }
1421  return tabs;
1422}
1423
1424std::vector<gfx::Rect> TabDragController::CalculateBoundsForDraggedTabs() {
1425  std::vector<gfx::Rect> drag_bounds;
1426  std::vector<Tab*> attached_tabs;
1427  for (size_t i = 0; i < drag_data_.size(); ++i)
1428    attached_tabs.push_back(drag_data_[i].attached_tab);
1429  attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs,
1430                                                    &drag_bounds);
1431  return drag_bounds;
1432}
1433
1434void TabDragController::EndDragImpl(EndDragType type) {
1435  DCHECK(active_);
1436  active_ = false;
1437
1438  bring_to_front_timer_.Stop();
1439  move_stacked_timer_.Stop();
1440
1441  if (is_dragging_window_) {
1442    waiting_for_run_loop_to_exit_ = true;
1443
1444    if (type == NORMAL || (type == TAB_DESTROYED && drag_data_.size() > 1)) {
1445      SetWindowPositionManaged(GetAttachedBrowserWidget()->GetNativeView(),
1446                               true);
1447    }
1448
1449    // End the nested drag loop.
1450    GetAttachedBrowserWidget()->EndMoveLoop();
1451  }
1452
1453  if (type != TAB_DESTROYED) {
1454    // We only finish up the drag if we were actually dragging. If start_drag_
1455    // is false, the user just clicked and released and didn't move the mouse
1456    // enough to trigger a drag.
1457    if (started_drag_) {
1458      RestoreFocus();
1459      if (type == CANCELED)
1460        RevertDrag();
1461      else
1462        CompleteDrag();
1463    }
1464  } else if (drag_data_.size() > 1) {
1465    initial_selection_model_.Clear();
1466    RevertDrag();
1467  }  // else case the only tab we were dragging was deleted. Nothing to do.
1468
1469  if (!detach_into_browser_)
1470    ResetDelegates();
1471
1472  // Clear out drag data so we don't attempt to do anything with it.
1473  drag_data_.clear();
1474
1475  TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
1476      attached_tabstrip_ : source_tabstrip_;
1477  owning_tabstrip->DestroyDragController();
1478}
1479
1480void TabDragController::RevertDrag() {
1481  std::vector<Tab*> tabs;
1482  for (size_t i = 0; i < drag_data_.size(); ++i) {
1483    if (drag_data_[i].contents) {
1484      // Contents is NULL if a tab was destroyed while the drag was under way.
1485      tabs.push_back(drag_data_[i].attached_tab);
1486      RevertDragAt(i);
1487    }
1488  }
1489
1490  bool restore_frame = !detach_into_browser_ &&
1491                       attached_tabstrip_ != source_tabstrip_;
1492  if (attached_tabstrip_) {
1493    if (did_restore_window_)
1494      MaximizeAttachedWindow();
1495    if (attached_tabstrip_ == source_tabstrip_) {
1496      source_tabstrip_->StoppedDraggingTabs(
1497          tabs, initial_tab_positions_, move_behavior_ == MOVE_VISIBILE_TABS,
1498          false);
1499    } else {
1500      attached_tabstrip_->DraggedTabsDetached();
1501    }
1502  }
1503
1504  if (initial_selection_model_.empty())
1505    ResetSelection(GetModel(source_tabstrip_));
1506  else
1507    GetModel(source_tabstrip_)->SetSelectionFromModel(initial_selection_model_);
1508
1509  // If we're not attached to any TabStrip, or attached to some other TabStrip,
1510  // we need to restore the bounds of the original TabStrip's frame, in case
1511  // it has been hidden.
1512  if (restore_frame && !restore_bounds_.IsEmpty())
1513    source_tabstrip_->GetWidget()->SetBounds(restore_bounds_);
1514
1515  if (detach_into_browser_ && source_tabstrip_)
1516    source_tabstrip_->GetWidget()->Activate();
1517
1518  // Return the WebContents to normalcy.  If the tab was attached to a
1519  // TabStrip before the revert, the decrement has already occurred.
1520  // If the tab was destroyed, don't attempt to dereference the
1521  // WebContents pointer.
1522  if (!detach_into_browser_ && !attached_tabstrip_ && source_dragged_contents())
1523    source_dragged_contents()->DecrementCapturerCount();
1524}
1525
1526void TabDragController::ResetSelection(TabStripModel* model) {
1527  DCHECK(model);
1528  ui::ListSelectionModel selection_model;
1529  bool has_one_valid_tab = false;
1530  for (size_t i = 0; i < drag_data_.size(); ++i) {
1531    // |contents| is NULL if a tab was deleted out from under us.
1532    if (drag_data_[i].contents) {
1533      int index = model->GetIndexOfWebContents(drag_data_[i].contents);
1534      DCHECK_NE(-1, index);
1535      selection_model.AddIndexToSelection(index);
1536      if (!has_one_valid_tab || i == source_tab_index_) {
1537        // Reset the active/lead to the first tab. If the source tab is still
1538        // valid we'll reset these again later on.
1539        selection_model.set_active(index);
1540        selection_model.set_anchor(index);
1541        has_one_valid_tab = true;
1542      }
1543    }
1544  }
1545  if (!has_one_valid_tab)
1546    return;
1547
1548  model->SetSelectionFromModel(selection_model);
1549}
1550
1551void TabDragController::RestoreInitialSelection() {
1552  // First time detaching from the source tabstrip. Reset selection model to
1553  // initial_selection_model_. Before resetting though we have to remove all
1554  // the tabs from initial_selection_model_ as it was created with the tabs
1555  // still there.
1556  ui::ListSelectionModel selection_model;
1557  selection_model.Copy(initial_selection_model_);
1558  for (DragData::const_reverse_iterator i(drag_data_.rbegin());
1559       i != drag_data_.rend(); ++i) {
1560    selection_model.DecrementFrom(i->source_model_index);
1561  }
1562  // We may have cleared out the selection model. Only reset it if it
1563  // contains something.
1564  if (selection_model.empty())
1565    return;
1566
1567  // The anchor/active may have been among the tabs that were dragged out. Force
1568  // the anchor/active to be valid.
1569  if (selection_model.anchor() == ui::ListSelectionModel::kUnselectedIndex)
1570    selection_model.set_anchor(selection_model.selected_indices()[0]);
1571  if (selection_model.active() == ui::ListSelectionModel::kUnselectedIndex)
1572    selection_model.set_active(selection_model.selected_indices()[0]);
1573  GetModel(source_tabstrip_)->SetSelectionFromModel(selection_model);
1574}
1575
1576void TabDragController::RevertDragAt(size_t drag_index) {
1577  DCHECK(started_drag_);
1578  DCHECK(source_tabstrip_);
1579
1580  base::AutoReset<bool> setter(&is_mutating_, true);
1581  TabDragData* data = &(drag_data_[drag_index]);
1582  if (attached_tabstrip_) {
1583    int index =
1584        GetModel(attached_tabstrip_)->GetIndexOfWebContents(data->contents);
1585    if (attached_tabstrip_ != source_tabstrip_) {
1586      // The Tab was inserted into another TabStrip. We need to put it back
1587      // into the original one.
1588      GetModel(attached_tabstrip_)->DetachWebContentsAt(index);
1589      // TODO(beng): (Cleanup) seems like we should use Attach() for this
1590      //             somehow.
1591      GetModel(source_tabstrip_)->InsertWebContentsAt(
1592          data->source_model_index, data->contents,
1593          (data->pinned ? TabStripModel::ADD_PINNED : 0));
1594    } else {
1595      // The Tab was moved within the TabStrip where the drag was initiated.
1596      // Move it back to the starting location.
1597      GetModel(source_tabstrip_)->MoveWebContentsAt(
1598          index, data->source_model_index, false);
1599    }
1600  } else {
1601    // The Tab was detached from the TabStrip where the drag began, and has not
1602    // been attached to any other TabStrip. We need to put it back into the
1603    // source TabStrip.
1604    GetModel(source_tabstrip_)->InsertWebContentsAt(
1605        data->source_model_index, data->contents,
1606        (data->pinned ? TabStripModel::ADD_PINNED : 0));
1607  }
1608}
1609
1610void TabDragController::CompleteDrag() {
1611  DCHECK(started_drag_);
1612
1613  if (attached_tabstrip_) {
1614    if (is_dragging_new_browser_ || did_restore_window_) {
1615      if (IsDockedOrSnapped(attached_tabstrip_)) {
1616        was_source_maximized_ = false;
1617        was_source_fullscreen_ = false;
1618      }
1619
1620      // If source window was maximized - maximize the new window as well.
1621      if (was_source_maximized_ || was_source_fullscreen_)
1622        MaximizeAttachedWindow();
1623    }
1624    attached_tabstrip_->StoppedDraggingTabs(
1625        GetTabsMatchingDraggedContents(attached_tabstrip_),
1626        initial_tab_positions_,
1627        move_behavior_ == MOVE_VISIBILE_TABS,
1628        true);
1629  } else {
1630    // Compel the model to construct a new window for the detached
1631    // WebContentses.
1632    views::Widget* widget = source_tabstrip_->GetWidget();
1633    gfx::Rect window_bounds(widget->GetRestoredBounds());
1634    window_bounds.set_origin(GetWindowCreatePoint(last_point_in_screen_));
1635
1636    base::AutoReset<bool> setter(&is_mutating_, true);
1637
1638    std::vector<TabStripModelDelegate::NewStripContents> contentses;
1639    for (size_t i = 0; i < drag_data_.size(); ++i) {
1640      TabStripModelDelegate::NewStripContents item;
1641      item.web_contents = drag_data_[i].contents;
1642      item.add_types = drag_data_[i].pinned ? TabStripModel::ADD_PINNED
1643                                            : TabStripModel::ADD_NONE;
1644      contentses.push_back(item);
1645    }
1646
1647    Browser* new_browser =
1648        GetModel(source_tabstrip_)->delegate()->CreateNewStripWithContents(
1649            contentses, window_bounds, widget->IsMaximized());
1650    ResetSelection(new_browser->tab_strip_model());
1651    new_browser->window()->Show();
1652
1653    // Return the WebContents to normalcy.
1654    if (!detach_into_browser_)
1655      source_dragged_contents()->DecrementCapturerCount();
1656  }
1657
1658  CleanUpHiddenFrame();
1659}
1660
1661void TabDragController::MaximizeAttachedWindow() {
1662  GetAttachedBrowserWidget()->Maximize();
1663#if defined(USE_ASH)
1664  if (was_source_fullscreen_ &&
1665      host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) {
1666    // In fullscreen mode it is only possible to get here if the source
1667    // was in "immersive fullscreen" mode, so toggle it back on.
1668    ash::accelerators::ToggleFullscreen();
1669  }
1670#endif
1671}
1672
1673void TabDragController::ResetDelegates() {
1674  DCHECK(!detach_into_browser_);
1675  for (size_t i = 0; i < drag_data_.size(); ++i) {
1676    if (drag_data_[i].contents &&
1677        drag_data_[i].contents->GetDelegate() == this) {
1678      drag_data_[i].contents->SetDelegate(
1679          drag_data_[i].original_delegate);
1680    }
1681  }
1682}
1683
1684gfx::Rect TabDragController::GetViewScreenBounds(
1685    views::View* view) const {
1686  gfx::Point view_topleft;
1687  views::View::ConvertPointToScreen(view, &view_topleft);
1688  gfx::Rect view_screen_bounds = view->GetLocalBounds();
1689  view_screen_bounds.Offset(view_topleft.x(), view_topleft.y());
1690  return view_screen_bounds;
1691}
1692
1693void TabDragController::CleanUpHiddenFrame() {
1694  // If the model we started dragging from is now empty, we must ask the
1695  // delegate to close the frame.
1696  if (!detach_into_browser_ && GetModel(source_tabstrip_)->empty())
1697    GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession();
1698}
1699
1700void TabDragController::BringWindowUnderPointToFront(
1701    const gfx::Point& point_in_screen) {
1702  aura::Window* window = GetLocalProcessWindow(point_in_screen, true);
1703
1704  // Only bring browser windows to front - only windows with a TabStrip can
1705  // be tab drag targets.
1706  if (!GetTabStripForWindow(window))
1707    return;
1708
1709  if (window) {
1710    views::Widget* widget_window = views::Widget::GetWidgetForNativeView(
1711        window);
1712    if (!widget_window)
1713      return;
1714
1715#if defined(USE_ASH)
1716    if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH) {
1717      // TODO(varkha): The code below ensures that the phantom drag widget
1718      // is shown on top of browser windows. The code should be moved to ash/
1719      // and the phantom should be able to assert its top-most state on its own.
1720      // One strategy would be for DragWindowController to
1721      // be able to observe stacking changes to the phantom drag widget's
1722      // siblings in order to keep it on top. One way is to implement a
1723      // notification that is sent to a window parent's observers when a
1724      // stacking order is changed among the children of that same parent.
1725      // Note that OnWindowStackingChanged is sent only to the child that is the
1726      // argument of one of the Window::StackChildX calls and not to all its
1727      // siblings affected by the stacking change.
1728      aura::Window* browser_window = widget_window->GetNativeView();
1729      // Find a topmost non-popup window and stack the recipient browser above
1730      // it in order to avoid stacking the browser window on top of the phantom
1731      // drag widget created by DragWindowController in a second display.
1732      for (aura::Window::Windows::const_reverse_iterator it =
1733           browser_window->parent()->children().rbegin();
1734           it != browser_window->parent()->children().rend(); ++it) {
1735        // If the iteration reached the recipient browser window then it is
1736        // already topmost and it is safe to return with no stacking change.
1737        if (*it == browser_window)
1738          return;
1739        if ((*it)->type() != ui::wm::WINDOW_TYPE_POPUP) {
1740          widget_window->StackAbove(*it);
1741          break;
1742        }
1743      }
1744    } else {
1745      widget_window->StackAtTop();
1746    }
1747#else
1748    widget_window->StackAtTop();
1749#endif
1750
1751    // The previous call made the window appear on top of the dragged window,
1752    // move the dragged window to the front.
1753    if (is_dragging_window_)
1754      attached_tabstrip_->GetWidget()->StackAtTop();
1755  }
1756}
1757
1758TabStripModel* TabDragController::GetModel(
1759    TabStrip* tabstrip) const {
1760  return static_cast<BrowserTabStripController*>(tabstrip->controller())->
1761      model();
1762}
1763
1764views::Widget* TabDragController::GetAttachedBrowserWidget() {
1765  return attached_tabstrip_->GetWidget();
1766}
1767
1768bool TabDragController::AreTabsConsecutive() {
1769  for (size_t i = 1; i < drag_data_.size(); ++i) {
1770    if (drag_data_[i - 1].source_model_index + 1 !=
1771        drag_data_[i].source_model_index) {
1772      return false;
1773    }
1774  }
1775  return true;
1776}
1777
1778gfx::Rect TabDragController::CalculateDraggedBrowserBounds(
1779    TabStrip* source,
1780    const gfx::Point& point_in_screen,
1781    std::vector<gfx::Rect>* drag_bounds) {
1782  gfx::Point center(0, source->height() / 2);
1783  views::View::ConvertPointToWidget(source, &center);
1784  gfx::Rect new_bounds(source->GetWidget()->GetRestoredBounds());
1785  if (source->GetWidget()->IsMaximized()) {
1786    // If the restore bounds is really small, we don't want to honor it
1787    // (dragging a really small window looks wrong), instead make sure the new
1788    // window is at least 50% the size of the old.
1789    const gfx::Size max_size(
1790        source->GetWidget()->GetWindowBoundsInScreen().size());
1791    new_bounds.set_width(
1792        std::max(max_size.width() / 2, new_bounds.width()));
1793    new_bounds.set_height(
1794        std::max(max_size.height() / 2, new_bounds.height()));
1795  }
1796  new_bounds.set_y(point_in_screen.y() - center.y());
1797  switch (GetDetachPosition(point_in_screen)) {
1798    case DETACH_BEFORE:
1799      new_bounds.set_x(point_in_screen.x() - center.x());
1800      new_bounds.Offset(-mouse_offset_.x(), 0);
1801      break;
1802    case DETACH_AFTER: {
1803      gfx::Point right_edge(source->width(), 0);
1804      views::View::ConvertPointToWidget(source, &right_edge);
1805      new_bounds.set_x(point_in_screen.x() - right_edge.x());
1806      new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0);
1807      OffsetX(-(*drag_bounds)[0].x(), drag_bounds);
1808      break;
1809    }
1810    default:
1811      break; // Nothing to do for DETACH_ABOVE_OR_BELOW.
1812  }
1813
1814  // To account for the extra vertical on restored windows that is absent on
1815  // maximized windows, add an additional vertical offset extracted from the tab
1816  // strip.
1817  if (source->GetWidget()->IsMaximized())
1818    new_bounds.Offset(0, -source->button_v_offset());
1819  return new_bounds;
1820}
1821
1822void TabDragController::AdjustBrowserAndTabBoundsForDrag(
1823    int last_tabstrip_width,
1824    const gfx::Point& point_in_screen,
1825    std::vector<gfx::Rect>* drag_bounds) {
1826  attached_tabstrip_->InvalidateLayout();
1827  attached_tabstrip_->DoLayout();
1828  const int dragged_tabstrip_width = attached_tabstrip_->tab_area_width();
1829
1830  // If the new tabstrip is smaller than the old resize the tabs.
1831  if (dragged_tabstrip_width < last_tabstrip_width) {
1832    const float leading_ratio =
1833        drag_bounds->front().x() / static_cast<float>(last_tabstrip_width);
1834    *drag_bounds = CalculateBoundsForDraggedTabs();
1835
1836    if (drag_bounds->back().right() < dragged_tabstrip_width) {
1837      const int delta_x =
1838          std::min(static_cast<int>(leading_ratio * dragged_tabstrip_width),
1839                   dragged_tabstrip_width -
1840                       (drag_bounds->back().right() -
1841                        drag_bounds->front().x()));
1842      OffsetX(delta_x, drag_bounds);
1843    }
1844
1845    // Reposition the restored window such that the tab that was dragged remains
1846    // under the mouse cursor.
1847    gfx::Point offset(
1848        static_cast<int>((*drag_bounds)[source_tab_index_].width() *
1849                         offset_to_width_ratio_) +
1850        (*drag_bounds)[source_tab_index_].x(), 0);
1851    views::View::ConvertPointToWidget(attached_tabstrip_, &offset);
1852    gfx::Rect bounds = GetAttachedBrowserWidget()->GetWindowBoundsInScreen();
1853    bounds.set_x(point_in_screen.x() - offset.x());
1854    GetAttachedBrowserWidget()->SetBounds(bounds);
1855  }
1856  attached_tabstrip_->SetTabBoundsForDrag(*drag_bounds);
1857}
1858
1859Browser* TabDragController::CreateBrowserForDrag(
1860    TabStrip* source,
1861    const gfx::Point& point_in_screen,
1862    gfx::Vector2d* drag_offset,
1863    std::vector<gfx::Rect>* drag_bounds) {
1864  gfx::Rect new_bounds(CalculateDraggedBrowserBounds(source,
1865                                                     point_in_screen,
1866                                                     drag_bounds));
1867  *drag_offset = point_in_screen - new_bounds.origin();
1868
1869  Profile* profile =
1870      Profile::FromBrowserContext(drag_data_[0].contents->GetBrowserContext());
1871  Browser::CreateParams create_params(Browser::TYPE_TABBED,
1872                                      profile,
1873                                      host_desktop_type_);
1874  create_params.initial_bounds = new_bounds;
1875  Browser* browser = new Browser(create_params);
1876  is_dragging_new_browser_ = true;
1877  SetWindowPositionManaged(browser->window()->GetNativeWindow(), false);
1878  // If the window is created maximized then the bounds we supplied are ignored.
1879  // We need to reset them again so they are honored.
1880  browser->window()->SetBounds(new_bounds);
1881
1882  return browser;
1883}
1884
1885gfx::Point TabDragController::GetCursorScreenPoint() {
1886#if defined(USE_ASH)
1887  if (host_desktop_type_ == chrome::HOST_DESKTOP_TYPE_ASH &&
1888      event_source_ == EVENT_SOURCE_TOUCH &&
1889      aura::Env::GetInstance()->is_touch_down()) {
1890    views::Widget* widget = GetAttachedBrowserWidget();
1891    DCHECK(widget);
1892    aura::Window* widget_window = widget->GetNativeWindow();
1893    DCHECK(widget_window->GetRootWindow());
1894    gfx::PointF touch_point_f;
1895    bool got_touch_point = ui::GestureRecognizer::Get()->
1896        GetLastTouchPointForTarget(widget_window, &touch_point_f);
1897    // TODO(tdresser): Switch to using gfx::PointF. See crbug.com/337824.
1898    gfx::Point touch_point = gfx::ToFlooredPoint(touch_point_f);
1899    DCHECK(got_touch_point);
1900    ash::wm::ConvertPointToScreen(widget_window->GetRootWindow(), &touch_point);
1901    return touch_point;
1902  }
1903#endif
1904  return screen_->GetCursorScreenPoint();
1905}
1906
1907gfx::Vector2d TabDragController::GetWindowOffset(
1908    const gfx::Point& point_in_screen) {
1909  TabStrip* owning_tabstrip = (attached_tabstrip_ && detach_into_browser_) ?
1910      attached_tabstrip_ : source_tabstrip_;
1911  views::View* toplevel_view = owning_tabstrip->GetWidget()->GetContentsView();
1912
1913  gfx::Point point = point_in_screen;
1914  views::View::ConvertPointFromScreen(toplevel_view, &point);
1915  return point.OffsetFromOrigin();
1916}
1917
1918gfx::NativeWindow TabDragController::GetLocalProcessWindow(
1919    const gfx::Point& screen_point,
1920    bool exclude_dragged_view) {
1921  std::set<aura::Window*> exclude;
1922  if (exclude_dragged_view) {
1923    aura::Window* dragged_window =
1924        attached_tabstrip_->GetWidget()->GetNativeView();
1925    if (dragged_window)
1926      exclude.insert(dragged_window);
1927  }
1928#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
1929  // Exclude windows which are pending deletion via Browser::TabStripEmpty().
1930  // These windows can be returned in the Linux Aura port because the browser
1931  // window which was used for dragging is not hidden once all of its tabs are
1932  // attached to another browser window in DragBrowserToNewTabStrip().
1933  // TODO(pkotwicz): Fix this properly (crbug.com/358482)
1934  BrowserList* browser_list = BrowserList::GetInstance(
1935      chrome::HOST_DESKTOP_TYPE_NATIVE);
1936  for (BrowserList::const_iterator it = browser_list->begin();
1937       it != browser_list->end(); ++it) {
1938    if ((*it)->tab_strip_model()->empty())
1939      exclude.insert((*it)->window()->GetNativeWindow());
1940  }
1941#endif
1942  return GetLocalProcessWindowAtPoint(host_desktop_type_,
1943                                      screen_point,
1944                                      exclude);
1945
1946}
1947