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