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