diff options
author | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-05 15:25:39 +0000 |
---|---|---|
committer | sky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-01-05 15:25:39 +0000 |
commit | c247b5158f9aaa01b256ae1e0fe4920e65b2f095 (patch) | |
tree | 2065927e8f41479ab6188cf22b558a75b40b501c | |
parent | 7e7f378a64278a0d738b631717de283ec305b02b (diff) | |
download | chromium_src-c247b5158f9aaa01b256ae1e0fe4920e65b2f095.zip chromium_src-c247b5158f9aaa01b256ae1e0fe4920e65b2f095.tar.gz chromium_src-c247b5158f9aaa01b256ae1e0fe4920e65b2f095.tar.bz2 |
Makes detached tab dragging create a real browser. It's enabled on
aura (not in compact mode) and behind a flag on windows.
BUG=98345
TEST=make sure tab dragging still works on aura and windows both with
and without the flag 'Enable Tab Browser Dragging' enabled.
R=ben@chromium.org
Review URL: http://codereview.chromium.org/8983025
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@116488 0039d316-1c4b-4281-b951-d872f2087c98
19 files changed, 1240 insertions, 467 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index b7ab046..f7c4364 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -5052,6 +5052,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_ENABLE_JAVASCRIPT_HARMONY_DESCRIPTION" desc="Description for the flag to enable JavaScript Harmony features."> Enable web pages to use experimental JavaScript features. </message> + <message name="IDS_FLAGS_ENABLE_TAB_BROWSER_DRAGGING_NAME" desc="Title for the flag to enable detached tabs to create a real browser."> + Enable Tab Browser Dragging + </message> + <message name="IDS_FLAGS_ENABLE_TAB_BROWSER_DRAGGING_DESCRIPTION" desc="Description for the flag to enable detached tabs to create a real browser."> + Enable creating a browser window when dragging tabs. + </message> <message name="IDS_FLAGS_ENABLE_RESTORE_SESSION_STATE_NAME" desc="Title for the flag to enable restoring (more) session state after restarts and crashes."> Better session restore </message> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index cf867b3..1faafce 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -504,6 +504,13 @@ const Experiment kExperiments[] = { SINGLE_VALUE_TYPE_AND_VALUE(switches::kJavaScriptFlags, "--harmony") }, { + "enable-tab-browser-dragging", + IDS_FLAGS_ENABLE_TAB_BROWSER_DRAGGING_NAME, + IDS_FLAGS_ENABLE_TAB_BROWSER_DRAGGING_DESCRIPTION, + kOsWin, + SINGLE_VALUE_TYPE(switches::kTabBrowserDragging) + }, + { "enable-restore-session-state", IDS_FLAGS_ENABLE_RESTORE_SESSION_STATE_NAME, IDS_FLAGS_ENABLE_RESTORE_SESSION_STATE_DESCRIPTION, diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc index b45a85a..8cc12f0 100644 --- a/chrome/browser/ui/views/frame/browser_view.cc +++ b/chrome/browser/ui/views/frame/browser_view.cc @@ -56,6 +56,7 @@ #include "chrome/browser/ui/views/default_search_view.h" #include "chrome/browser/ui/views/download/download_in_progress_dialog_view.h" #include "chrome/browser/ui/views/frame/browser_view_layout.h" +#include "chrome/browser/ui/views/frame/browser_window_move_observer.h" #include "chrome/browser/ui/views/frame/contents_container.h" #include "chrome/browser/ui/views/fullscreen_exit_bubble_views.h" #include "chrome/browser/ui/views/infobars/infobar_container_view.h" @@ -333,7 +334,8 @@ BrowserView::BrowserView(Browser* browser) hung_window_detector_(&hung_plugin_action_), ticker_(0), #endif - force_location_bar_focus_(false) { + force_location_bar_focus_(false), + move_observer_(NULL) { browser_->tabstrip_model()->AddObserver(this); registrar_.Add( @@ -1683,6 +1685,9 @@ void BrowserView::OnWidgetMove() { return; } + if (move_observer_) + move_observer_->OnWidgetMoved(); + // Cancel any tabstrip animations, some of them may be invalidated by the // window being repositioned. // Comment out for one cycle to see if this fixes dist tests. diff --git a/chrome/browser/ui/views/frame/browser_view.h b/chrome/browser/ui/views/frame/browser_view.h index 5cbc987..c1e7436 100644 --- a/chrome/browser/ui/views/frame/browser_view.h +++ b/chrome/browser/ui/views/frame/browser_view.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -41,6 +41,7 @@ class BookmarkBarView; class Browser; class BrowserViewLayout; +class BrowserWindowMoveObserver; class ContentsContainer; class DownloadShelfView; class EncodingMenuModel; @@ -242,6 +243,10 @@ class BrowserView : public BrowserWindow, // when a new browser window is created. void RestoreFocus(); + void set_move_observer(BrowserWindowMoveObserver* observer) { + move_observer_ = observer; + } + // Overridden from BrowserWindow: virtual void Show() OVERRIDE; virtual void ShowInactive() OVERRIDE; @@ -745,6 +750,8 @@ class BrowserView : public BrowserWindow, PendingFullscreenRequest fullscreen_request_; + BrowserWindowMoveObserver* move_observer_; + DISALLOW_COPY_AND_ASSIGN(BrowserView); }; diff --git a/chrome/browser/ui/views/frame/browser_window_move_observer.h b/chrome/browser/ui/views/frame/browser_window_move_observer.h new file mode 100644 index 0000000..ac4b54e --- /dev/null +++ b/chrome/browser/ui/views/frame/browser_window_move_observer.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_WINDOW_MOVE_OBSERVER_H_ +#define CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_WINDOW_MOVE_OBSERVER_H_ +#pragma once + +// BrowserView notifies BrowserWindowMoveObserver as the window moves. +class BrowserWindowMoveObserver { + public: + virtual void OnWidgetMoved() = 0; + + protected: + ~BrowserWindowMoveObserver() {} +}; + +#endif // CHROME_BROWSER_UI_VIEWS_FRAME_BROWSER_WINDOW_MOVE_OBSERVER_H_ diff --git a/chrome/browser/ui/views/tabs/default_tab_drag_controller.cc b/chrome/browser/ui/views/tabs/default_tab_drag_controller.cc index b691495..8486144 100644 --- a/chrome/browser/ui/views/tabs/default_tab_drag_controller.cc +++ b/chrome/browser/ui/views/tabs/default_tab_drag_controller.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -8,6 +8,7 @@ #include <set> #include "base/callback.h" +#include "base/command_line.h" #include "base/i18n/rtl.h" #include "chrome/browser/extensions/extension_function_dispatcher.h" #include "chrome/browser/tabs/tab_strip_model.h" @@ -20,6 +21,8 @@ #include "chrome/browser/ui/views/tabs/native_view_photobooth.h" #include "chrome/browser/ui/views/tabs/tab.h" #include "chrome/browser/ui/views/tabs/tab_strip.h" +#include "chrome/common/chrome_switches.h" +#include "content/browser/tab_contents/tab_contents.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" @@ -36,8 +39,18 @@ #include "ui/gfx/canvas_skia.h" #include "ui/gfx/screen.h" #include "ui/views/events/event.h" +#include "ui/views/widget/root_view.h" #include "ui/views/widget/widget.h" +#if defined(USE_AURA) +#include "ash/shell.h" +#include "chrome/browser/ui/views/tabs/tab_drag_controller2.h" +#endif + +#if defined(OS_WIN) +#include "chrome/browser/ui/views/tabs/tab_drag_controller2.h" +#endif + #if defined(TOOLKIT_USES_GTK) #include <gdk/gdk.h> // NOLINT #include <gdk/gdkkeysyms.h> // NOLINT @@ -324,6 +337,7 @@ DefaultTabDragController::~DefaultTabDragController() { instance_ = NULL; MessageLoopForUI::current()->RemoveObserver(this); + // Need to delete the view here manually _before_ we reset the dragged // contents to NULL, otherwise if the view is animating to its destination // bounds, it won't be able to clean up properly since its cleanup routine @@ -401,6 +415,11 @@ void DefaultTabDragController::Drag() { started_drag_ = true; SaveFocus(); Attach(source_tabstrip_, gfx::Point()); + // Redirect all mouse events to the TabStrip so that the tab that + // originated the drag can safely be deleted. + static_cast<views::internal::RootView*>( + source_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler( + source_tabstrip_); } ContinueDragging(); @@ -1417,6 +1436,17 @@ bool DefaultTabDragController::AreTabsConsecutive() { return true; } +#if defined(USE_AURA) || defined(OS_WIN) +static bool ShouldCreateTabDragController2() { +#if defined(USE_AURA) + return !ash::Shell::GetInstance()->IsWindowModeCompact(); +#else + return CommandLine::ForCurrentProcess()->HasSwitch( + switches::kTabBrowserDragging); +#endif +} +#endif + // static TabDragController* TabDragController::Create( TabStrip* source_tabstrip, @@ -1425,6 +1455,14 @@ TabDragController* TabDragController::Create( const gfx::Point& mouse_offset, int source_tab_offset, const TabStripSelectionModel& initial_selection_model) { +#if defined(USE_AURA) || defined(OS_WIN) + if (ShouldCreateTabDragController2()) { + TabDragController2* controller = new TabDragController2; + controller->Init(source_tabstrip, source_tab, tabs, mouse_offset, + source_tab_offset, initial_selection_model); + return controller; + } +#endif DefaultTabDragController* controller = new DefaultTabDragController; controller->Init(source_tabstrip, source_tab, tabs, mouse_offset, source_tab_offset, initial_selection_model); @@ -1433,6 +1471,21 @@ TabDragController* TabDragController::Create( // static bool TabDragController::IsAttachedTo(TabStrip* tab_strip) { - return instance_ && instance_->active()&& - instance_->attached_tabstrip() == tab_strip; +#if defined(USE_AURA) || defined(OS_WIN) + return TabDragController2::IsActiveAndAttachedTo(tab_strip) || + (instance_ && instance_->active() && + instance_->attached_tabstrip() == tab_strip); +#else + return (instance_ && instance_->active() && + instance_->attached_tabstrip() == tab_strip); +#endif +} + +// static +bool TabDragController::IsActive() { +#if defined(USE_AURA) || defined(OS_WIN) + return TabDragController2::IsActive() || (instance_ && instance_->active()); +#else + return instance_ && instance_->active(); +#endif } diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller.h b/chrome/browser/ui/views/tabs/tab_drag_controller.h index 4582979..68e9211 100644 --- a/chrome/browser/ui/views/tabs/tab_drag_controller.h +++ b/chrome/browser/ui/views/tabs/tab_drag_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -53,6 +53,9 @@ class TabDragController { // finishing the drag. static bool IsAttachedTo(TabStrip* tab_strip); + // Returns true if there is a drag underway. + static bool IsActive(); + // Responds to drag events subsequent to StartDrag. If the mouse moves a // sufficient distance before the mouse is released, a drag session is // initiated. diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller2.cc b/chrome/browser/ui/views/tabs/tab_drag_controller2.cc index d22c93f..8ad4114 100644 --- a/chrome/browser/ui/views/tabs/tab_drag_controller2.cc +++ b/chrome/browser/ui/views/tabs/tab_drag_controller2.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -13,19 +13,21 @@ #include "chrome/browser/tabs/tab_strip_model.h" #include "chrome/browser/ui/app_modal_dialogs/message_box_handler.h" #include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "chrome/browser/ui/views/tabs/base_tab.h" #include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h" #include "chrome/browser/ui/views/tabs/dragged_tab_view.h" -#include "chrome/browser/ui/views/tabs/native_view_photobooth.h" #include "chrome/browser/ui/views/tabs/tab.h" #include "chrome/browser/ui/views/tabs/tab_strip.h" +#include "chrome/common/chrome_notification_types.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/public/browser/notification_details.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_source.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/user_metrics.h" +#include "content/public/browser/web_contents.h" #include "grit/theme_resources.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/animation/animation.h" @@ -36,21 +38,16 @@ #include "ui/gfx/canvas_skia.h" #include "ui/gfx/screen.h" #include "ui/views/events/event.h" +#include "ui/views/widget/root_view.h" #include "ui/views/widget/widget.h" -#if defined(TOOLKIT_USES_GTK) -#include <gdk/gdk.h> // NOLINT -#include <gdk/gdkkeysyms.h> // NOLINT -#endif - -using content::OpenURLParams; using content::UserMetricsAction; using content::WebContents; static const int kHorizontalMoveThreshold = 16; // Pixels. // If non-null there is a drag underway. -static TabDragController2* instance_ = NULL; +static TabDragController2* instance_; namespace { @@ -196,9 +193,7 @@ class TabDragController2::DockDisplayer : public ui::AnimationDelegate { ALLOW_THIS_IN_INITIALIZER_LIST(animation_(this)), hidden_(false), in_enable_area_(info.in_enable_area()) { -#if defined(OS_WIN) popup_ = new views::Widget; - // TODO(sky): This should "just work" on Gtk now. views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); params.transparent = true; params.keep_on_top = true; @@ -211,9 +206,6 @@ class TabDragController2::DockDisplayer : public ui::AnimationDelegate { else animation_.Show(); popup_->Show(); -#else - NOTIMPLEMENTED(); -#endif popup_view_ = popup_->GetNativeView(); } @@ -230,8 +222,8 @@ class TabDragController2::DockDisplayer : public ui::AnimationDelegate { } } - // Resets the reference to the hosting TabDragController2. This is - // invoked when the TabDragController2 is destroyed. + // Resets the reference to the hosting TabDragController2. This is invoked + // when the TabDragController2 is destoryed. void clear_controller() { controller_ = NULL; } // NativeView of the window we create. @@ -293,7 +285,6 @@ class TabDragController2::DockDisplayer : public ui::AnimationDelegate { TabDragController2::TabDragData::TabDragData() : contents(NULL), - original_delegate(NULL), source_model_index(-1), attached_tab(NULL), pinned(false) { @@ -308,6 +299,7 @@ TabDragController2::TabDragData::~TabDragData() { TabDragController2::TabDragController2() : source_tabstrip_(NULL), attached_tabstrip_(NULL), + is_dragging_window_(false), source_tab_offset_(0), offset_to_width_ratio_(0), old_focused_view_(NULL), @@ -315,7 +307,12 @@ TabDragController2::TabDragController2() started_drag_(false), active_(true), source_tab_index_(std::numeric_limits<size_t>::max()), - initial_move_(true) { + initial_move_(true), + end_run_loop_behavior_(END_RUN_LOOP_STOP_DRAGGING), + waiting_for_run_loop_to_exit_(false), + tab_strip_to_attach_to_after_exit_(NULL), + move_loop_browser_view_(NULL), + destroyed_(NULL) { instance_ = this; } @@ -323,16 +320,16 @@ TabDragController2::~TabDragController2() { if (instance_ == this) instance_ = NULL; + if (destroyed_) + *destroyed_ = true; + + if (move_loop_browser_view_) + move_loop_browser_view_->set_move_observer(NULL); + + if (source_tabstrip_) + GetModel(source_tabstrip_)->RemoveObserver(this); + MessageLoopForUI::current()->RemoveObserver(this); - // Need to delete the view here manually _before_ we reset the dragged - // contents to NULL, otherwise if the view is animating to its destination - // bounds, it won't be able to clean up properly since its cleanup routine - // uses GetIndexForDraggedContents, which will be invalid. - view_.reset(NULL); - - // Reset the delegate of the dragged TabContents. This ends up doing nothing - // if the drag was completed. - ResetDelegates(); } void TabDragController2::Init( @@ -345,6 +342,7 @@ void TabDragController2::Init( DCHECK(!tabs.empty()); DCHECK(std::find(tabs.begin(), tabs.end(), source_tab) != tabs.end()); source_tabstrip_ = source_tabstrip; + GetModel(source_tabstrip_)->AddObserver(this); source_tab_offset_ = source_tab_offset; start_screen_point_ = GetCursorScreenPoint(); mouse_offset_ = mouse_offset; @@ -362,13 +360,19 @@ void TabDragController2::Init( offset_to_width_ratio_ = static_cast<float>(source_tab_offset_) / static_cast<float>(source_tab->width()); } - InitWindowCreatePoint(); + InitWindowCreatePoint(source_tabstrip); initial_selection_model_.Copy(initial_selection_model); +} - registrar_.Add( - this, - content::NOTIFICATION_WEB_CONTENTS_DELEGATE_DESTROYED, - content::NotificationService::AllSources()); +// static +bool TabDragController2::IsActiveAndAttachedTo(TabStrip* tab_strip) { + return instance_ && instance_->active_ && + instance_->attached_tabstrip_ == tab_strip; +} + +// static +bool TabDragController2::IsActive() { + return instance_ && instance_->active_; } void TabDragController2::InitTabDragData(BaseTab* tab, @@ -382,18 +386,14 @@ void TabDragController2::InitTabDragData(BaseTab* tab, this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, content::Source<WebContents>(drag_data->contents->tab_contents())); - - // We need to be the delegate so we receive messages about stuff, otherwise - // our dragged TabContents may be replaced and subsequently - // collected/destroyed while the drag is in process, leading to nasty crashes. - drag_data->original_delegate = - drag_data->contents->tab_contents()->GetDelegate(); - drag_data->contents->tab_contents()->SetDelegate(this); } void TabDragController2::Drag() { bring_to_front_timer_.Stop(); + if (waiting_for_run_loop_to_exit_) + return; + if (!started_drag_) { if (!CanStartDrag()) return; // User hasn't dragged far enough yet. @@ -401,13 +401,19 @@ void TabDragController2::Drag() { started_drag_ = true; SaveFocus(); Attach(source_tabstrip_, gfx::Point()); + if (static_cast<int>(drag_data_.size()) == + GetModel(source_tabstrip_)->count()) { + RunMoveLoop(); // Runs a nested loop, returning when done. + return; + } } ContinueDragging(); } void TabDragController2::EndDrag(bool canceled) { - EndDragImpl(canceled ? CANCELED : NORMAL); + // We can't revert if the source tabstrip was destroyed during the drag. + EndDragImpl(canceled && source_tabstrip_ ? CANCELED : NORMAL); } bool TabDragController2::GetStartedDrag() const { @@ -415,88 +421,17 @@ bool TabDragController2::GetStartedDrag() const { } /////////////////////////////////////////////////////////////////////////////// -// TabDragController2, PageNavigator implementation: - -WebContents* TabDragController2::OpenURLFromTab( - WebContents* source, - const OpenURLParams& params) { - if (source_tab_drag_data()->original_delegate) { - OpenURLParams forward_params = params; - if (params.disposition == CURRENT_TAB) - forward_params.disposition = NEW_WINDOW; - - return source_tab_drag_data()->original_delegate->OpenURLFromTab( - source, forward_params); - } - return NULL; -} - -/////////////////////////////////////////////////////////////////////////////// -// TabDragController2, content::WebContentsDelegate implementation: - -void TabDragController2::NavigationStateChanged(const WebContents* source, - unsigned changed_flags) { - if (view_.get()) - view_->Update(); -} - -void TabDragController2::AddNewContents(WebContents* source, - WebContents* new_contents, - WindowOpenDisposition disposition, - const gfx::Rect& initial_pos, - bool user_gesture) { - DCHECK_NE(CURRENT_TAB, disposition); - - // Theoretically could be called while dragging if the page tries to - // spawn a window. Route this message back to the browser in most cases. - if (source_tab_drag_data()->original_delegate) { - source_tab_drag_data()->original_delegate->AddNewContents( - source, new_contents, disposition, initial_pos, user_gesture); - } -} - -void TabDragController2::LoadingStateChanged(WebContents* source) { - // It would be nice to respond to this message by changing the - // screen shot in the dragged tab. - if (view_.get()) - view_->Update(); -} - -bool TabDragController2::ShouldSuppressDialogs() { - // When a dialog is about to be shown we revert the drag. Otherwise a modal - // dialog might appear and attempt to parent itself to a hidden tabcontents. - EndDragImpl(CANCELED); - return false; -} - -content::JavaScriptDialogCreator* -TabDragController2::GetJavaScriptDialogCreator() { - return GetJavaScriptDialogCreatorInstance(); -} - -/////////////////////////////////////////////////////////////////////////////// // TabDragController2, content::NotificationObserver implementation: void TabDragController2::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { - if (type == content::NOTIFICATION_WEB_CONTENTS_DELEGATE_DESTROYED) { - content::WebContentsDelegate* delegate = - content::Source<content::WebContentsDelegate>(source).ptr(); - for (size_t i = 0; i < drag_data_.size(); ++i) - CHECK_NE(delegate, drag_data_[i].original_delegate); - return; - } DCHECK_EQ(type, content::NOTIFICATION_WEB_CONTENTS_DESTROYED); WebContents* destroyed_contents = content::Source<WebContents>(source).ptr(); for (size_t i = 0; i < drag_data_.size(); ++i) { if (drag_data_[i].contents->tab_contents() == destroyed_contents) { - // One of the tabs we're dragging has been destroyed. Cancel the drag. - if (destroyed_contents->GetDelegate() == this) - destroyed_contents->SetDelegate(NULL); drag_data_[i].contents = NULL; - drag_data_[i].original_delegate = NULL; EndDragImpl(TAB_DESTROYED); return; } @@ -524,30 +459,31 @@ void TabDragController2::DidProcessEvent(const base::NativeEvent& event) { EndDrag(true); } } -#elif defined(TOOLKIT_USES_GTK) -void TabDragController2::WillProcessEvent(GdkEvent* event) { +#endif + +void TabDragController2::OnWidgetMoved() { + Drag(); } -void TabDragController2::DidProcessEvent(GdkEvent* event) { - if (event->type == GDK_KEY_PRESS && - reinterpret_cast<GdkEventKey*>(event)->keyval == GDK_Escape) { - EndDrag(true); - } +void TabDragController2::TabStripEmpty() { + GetModel(source_tabstrip_)->RemoveObserver(this); + // NULL out source_tabstrip_ so that we don't attempt to add back to in (in + // the case of a revert). + source_tabstrip_ = NULL; } -#endif /////////////////////////////////////////////////////////////////////////////// // TabDragController2, private: -void TabDragController2::InitWindowCreatePoint() { +void TabDragController2::InitWindowCreatePoint(TabStrip* tab_strip) { // window_create_point_ is only used in CompleteDrag() (through // GetWindowCreatePoint() to get the start point of the docked window) when // the attached_tabstrip_ is NULL and all the window's related bound - // information are obtained from source_tabstrip_. So, we need to get the - // first_tab based on source_tabstrip_, not attached_tabstrip_. Otherwise, + // information are obtained from tab_strip. So, we need to get the + // first_tab based on tab_strip, not attached_tabstrip_. Otherwise, // the window_create_point_ is not in the correct coordinate system. Please // refer to http://crbug.com/6223 comment #15 for detailed information. - views::View* first_tab = source_tabstrip_->base_tab_at_tab_index(0); + views::View* first_tab = tab_strip->base_tab_at_tab_index(0); views::View::ConvertPointToWidget(first_tab, &first_source_tab_point_); window_create_point_ = first_source_tab_point_; window_create_point_.Offset(mouse_offset_.x(), mouse_offset_.y()); @@ -609,6 +545,7 @@ void TabDragController2::UpdateDockInfo(const gfx::Point& screen_point) { void TabDragController2::SaveFocus() { DCHECK(!old_focused_view_); // This should only be invoked once. + DCHECK(source_tabstrip_); old_focused_view_ = source_tabstrip_->GetFocusManager()->GetFocusedView(); source_tabstrip_->GetFocusManager()->SetFocusedView(source_tabstrip_); } @@ -631,6 +568,8 @@ bool TabDragController2::CanStartDrag() const { } void TabDragController2::ContinueDragging() { + DCHECK(attached_tabstrip_); + // Note that the coordinates given to us by |drag_event| are basically // useless, since they're in source_tab_ coordinates. On the surface, you'd // think we could just convert them to screen coordinates, however in the @@ -643,24 +582,42 @@ void TabDragController2::ContinueDragging() { // guaranteed to be correct regardless of monitor config. gfx::Point screen_point = GetCursorScreenPoint(); -#if defined(OS_WIN) && !defined(USE_AURA) - // Currently only allowed on windows (and not aura). // Determine whether or not we have dragged over a compatible TabStrip in // another browser window. If we have, we should attach to it and start // dragging within it. TabStrip* target_tabstrip = GetTabStripForPoint(screen_point); -#else - TabStrip* target_tabstrip = source_tabstrip_; -#endif - if (target_tabstrip != attached_tabstrip_) { - // Make sure we're fully detached from whatever TabStrip we're attached to - // (if any). - if (attached_tabstrip_) - Detach(); - if (target_tabstrip) - Attach(target_tabstrip, screen_point); + bool tab_strip_changed = (target_tabstrip != attached_tabstrip_); + if (tab_strip_changed) { + if (!target_tabstrip) { + DetachIntoNewBrowserAndRunMoveLoop(screen_point); + return; + } + if (is_dragging_window_) { + BrowserView* browser_view = GetAttachedBrowserView(); + // Need to release the drag controller before starting the move loop as + // it's going to trigger capture lost, which cancels drag. + attached_tabstrip_->ReleaseDragController(); + target_tabstrip->OwnDragController(this); + // Disable animations so that we don't see a close animation on aero. + browser_view->GetWidget()->SetVisibilityChangedAnimationsEnabled(false); + browser_view->GetWidget()->ReleaseMouseCapture(); + // EndMoveLoop is going to snap the window back to it's original location. + // Hide it so users don't see this. + browser_view->GetWidget()->Hide(); + browser_view->GetWidget()->EndMoveLoop(); + tab_strip_to_attach_to_after_exit_ = target_tabstrip; + waiting_for_run_loop_to_exit_ = true; + end_run_loop_behavior_ = END_RUN_LOOP_CONTINUE_DRAGGING; + // Ideally we would swap the tabs now, but on windows it seems that + // running the move loop implicitly activates the window when done, + // leading to all sorts of flicker. So, instead we process the move after + // the loop completes. + return; + } + Detach(); + Attach(target_tabstrip, screen_point); } - if (!target_tabstrip) { + if (is_dragging_window_) { bring_to_front_timer_.Start(FROM_HERE, base::TimeDelta::FromMilliseconds(kBringToFrontDelay), this, &TabDragController2::BringWindowUnderMouseToFront); @@ -668,15 +625,19 @@ void TabDragController2::ContinueDragging() { UpdateDockInfo(screen_point); - if (attached_tabstrip_) + if (!is_dragging_window_) { MoveAttached(screen_point); - else - MoveDetached(screen_point); + if (tab_strip_changed) { + // Move the corresponding window to the front. We do this after the move + // as on windows activate triggers a synchronous paint. + attached_tabstrip_->GetWidget()->Activate(); + } + } } void TabDragController2::MoveAttached(const gfx::Point& screen_point) { DCHECK(attached_tabstrip_); - DCHECK(!view_.get()); + DCHECK(!is_dragging_window_); gfx::Point dragged_view_point = GetAttachedDragPoint(screen_point); @@ -736,16 +697,25 @@ void TabDragController2::MoveAttached(const gfx::Point& screen_point) { initial_move_ = false; } -void TabDragController2::MoveDetached(const gfx::Point& screen_point) { - DCHECK(!attached_tabstrip_); - DCHECK(view_.get()); - - // Move the View. There are no changes to the model if we're detached. - view_->MoveTo(screen_point); +TabDragController2::DetachPosition TabDragController2::GetDetachPosition( + const gfx::Point& screen_point) { + DCHECK(attached_tabstrip_); + gfx::Point attached_point(screen_point); + views::View::ConvertPointToView(NULL, attached_tabstrip_, &attached_point); + if (attached_point.x() < 0) + return DETACH_BEFORE; + if (attached_point.x() >= attached_tabstrip_->width()) + return DETACH_AFTER; + return DETACH_ABOVE_OR_BELOW; } DockInfo TabDragController2::GetDockInfoAtPoint( const gfx::Point& screen_point) { + // TODO: add support for dock info. + if (true) { + return DockInfo(); + } + if (attached_tabstrip_) { // If the mouse is over a tab strip, don't offer a dock position. return DockInfo(); @@ -758,45 +728,50 @@ DockInfo TabDragController2::GetDockInfoAtPoint( return dock_info_; } - gfx::NativeView dragged_hwnd = view_->GetWidget()->GetNativeView(); - dock_windows_.insert(dragged_hwnd); + // gfx::NativeView dragged_hwnd = view_->GetWidget()->GetNativeView(); + // dock_windows_.insert(dragged_hwnd); DockInfo info = DockInfo::GetDockInfoAtPoint(screen_point, dock_windows_); - dock_windows_.erase(dragged_hwnd); + // dock_windows_.erase(dragged_hwnd); return info; } -#if defined(OS_WIN) && !defined(USE_AURA) TabStrip* TabDragController2::GetTabStripForPoint( const gfx::Point& screen_point) { gfx::NativeView dragged_view = NULL; - if (view_.get()) { - dragged_view = view_->GetWidget()->GetNativeView(); + if (is_dragging_window_) { + dragged_view = attached_tabstrip_->GetWidget()->GetNativeView(); dock_windows_.insert(dragged_view); } gfx::NativeWindow local_window = DockInfo::GetLocalProcessWindowAtPoint(screen_point, dock_windows_); if (dragged_view) dock_windows_.erase(dragged_view); - if (!local_window) + TabStrip* tab_strip = GetTabStripForWindow(local_window); + if (tab_strip && DoesTabStripContain(tab_strip, screen_point)) + return tab_strip; + return is_dragging_window_ ? attached_tabstrip_ : NULL; +} + +TabStrip* TabDragController2::GetTabStripForWindow( + gfx::NativeWindow window) { + if (!window) return NULL; - BrowserView* browser = - BrowserView::GetBrowserViewForNativeWindow(local_window); - // We don't allow drops on windows that don't have tabstrips. + BrowserView* browser = BrowserView::GetBrowserViewForNativeWindow(window); + // We only allow drops on windows that have tabstrips. if (!browser || !browser->browser()->SupportsWindowFeature(Browser::FEATURE_TABSTRIP)) return NULL; - // This cast seems ugly, but the controller and the view are tighly coupled at - // creation time, so it will be okay. TabStrip* other_tabstrip = static_cast<TabStrip*>(browser->tabstrip()); - - if (!other_tabstrip->controller()->IsCompatibleWith(source_tabstrip_)) + TabStrip* tab_strip = + attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_; + DCHECK(tab_strip); + if (!other_tabstrip->controller()->IsCompatibleWith(tab_strip)) return NULL; - return GetTabStripIfItContains(other_tabstrip, screen_point); + return other_tabstrip; } -#endif -TabStrip* TabDragController2::GetTabStripIfItContains( +bool TabDragController2::DoesTabStripContain( TabStrip* tabstrip, const gfx::Point& screen_point) const { static const int kVerticalDetachMagnetism = 15; @@ -809,12 +784,10 @@ TabStrip* TabDragController2::GetTabStripIfItContains( // for the source TabStrip. int upper_threshold = tabstrip_bounds.bottom() + kVerticalDetachMagnetism; int lower_threshold = tabstrip_bounds.y() - kVerticalDetachMagnetism; - if (screen_point.y() >= lower_threshold && - screen_point.y() <= upper_threshold) { - return tabstrip; - } + return screen_point.y() >= lower_threshold && + screen_point.y() <= upper_threshold; } - return NULL; + return false; } void TabDragController2::Attach(TabStrip* attached_tabstrip, @@ -824,28 +797,15 @@ void TabDragController2::Attach(TabStrip* attached_tabstrip, attached_tabstrip_ = attached_tabstrip; - // And we don't need the dragged view. - view_.reset(); - std::vector<BaseTab*> tabs = GetTabsMatchingDraggedContents(attached_tabstrip_); if (tabs.empty()) { - // There is no Tab in |attached_tabstrip| that corresponds to the dragged - // TabContents. We must now create one. + // Transitioning from detached to attached to a new tabstrip. Add tabs to + // the new model. selection_model_before_attach_.Copy(attached_tabstrip->GetSelectionModel()); - // Remove ourselves as the delegate now that the dragged TabContents is - // being inserted back into a Browser. - for (size_t i = 0; i < drag_data_.size(); ++i) { - drag_data_[i].contents->tab_contents()->SetDelegate(NULL); - drag_data_[i].original_delegate = NULL; - } - - // Return the TabContents' to normalcy. - source_dragged_contents()->tab_contents()->SetCapturingContents(false); - // Inserting counts as a move. We don't want the tabs to jitter when the // user moves the tab immediately after attaching it. last_move_screen_loc_ = MajorAxisValue(screen_point, attached_tabstrip); @@ -892,27 +852,16 @@ void TabDragController2::Attach(TabStrip* attached_tabstrip, tabs[source_tab_index_]->width()); mouse_offset_.set_x(new_x); - // Move the corresponding window to the front. - attached_tabstrip_->GetWidget()->Activate(); + // Redirect all mouse events to the TabStrip so that the tab that originated + // the drag can safely be deleted. + static_cast<views::internal::RootView*>( + attached_tabstrip_->GetWidget()->GetRootView())->SetMouseHandler( + attached_tabstrip_); } void TabDragController2::Detach() { - // Prevent the TabContents' HWND from being hidden by any of the model - // operations performed during the drag. - source_dragged_contents()->tab_contents()->SetCapturingContents(true); - - // Calculate the drag bounds. - std::vector<gfx::Rect> drag_bounds; - std::vector<BaseTab*> attached_tabs; - for (size_t i = 0; i < drag_data_.size(); ++i) - attached_tabs.push_back(drag_data_[i].attached_tab); - attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs, - &drag_bounds); - TabStripModel* attached_model = GetModel(attached_tabstrip_); - std::vector<TabRendererData> tab_data; for (size_t i = 0; i < drag_data_.size(); ++i) { - tab_data.push_back(drag_data_[i].attached_tab->data()); int index = attached_model->GetIndexOfTabContents(drag_data_[i].contents); DCHECK_NE(-1, index); @@ -921,48 +870,137 @@ void TabDragController2::Detach() { attached_model->DetachTabContentsAt(index); - // Detaching resets the delegate, but we still want to be the delegate. - drag_data_[i].contents->tab_contents()->SetDelegate(this); - // Detaching may end up deleting the tab, drop references to it. drag_data_[i].attached_tab = NULL; } - // If we've removed the last Tab from the TabStrip, hide the frame now. - if (attached_model->empty()) { - HideFrame(); - } else if (!selection_model_before_attach_.empty() && - selection_model_before_attach_.active() >= 0 && - selection_model_before_attach_.active() < - attached_model->count()) { - // Restore the selection. - attached_model->SetSelectionFromModel(selection_model_before_attach_); - } else if (attached_tabstrip_ == source_tabstrip_ && - !initial_selection_model_.empty()) { - // First time detaching from the source tabstrip. Reset selection model to - // initial_selection_model_. Before resetting though we have to remove all - // the tabs from initial_selection_model_ as it was created with the tabs - // still there. - TabStripSelectionModel selection_model; - selection_model.Copy(initial_selection_model_); - for (DragData::const_reverse_iterator i = drag_data_.rbegin(); - i != drag_data_.rend(); ++i) { - selection_model.DecrementFrom(i->source_model_index); - } - // We may have cleared out the selection model. Only reset it if it contains - // something. - if (!selection_model.empty()) + if (!attached_model->empty()) { + if (!selection_model_before_attach_.empty() && + selection_model_before_attach_.active() >= 0 && + selection_model_before_attach_.active() < + attached_model->count()) { + // Restore the selection. + attached_model->SetSelectionFromModel(selection_model_before_attach_); + } else if (attached_tabstrip_ == source_tabstrip_ && + !initial_selection_model_.empty()) { + // First time detaching from the source tabstrip. Reset selection model to + // initial_selection_model_. Before resetting though we have to remove all + // the tabs from initial_selection_model_ as it was created with the tabs + // still there. + TabStripSelectionModel selection_model; + selection_model.Copy(initial_selection_model_); + for (DragData::const_reverse_iterator i = drag_data_.rbegin(); + i != drag_data_.rend(); ++i) { + selection_model.DecrementFrom(i->source_model_index); + } attached_model->SetSelectionFromModel(selection_model); + // We may have cleared out the selection model. Only reset it if it + // contains something. + if (!selection_model.empty()) + attached_model->SetSelectionFromModel(selection_model); + } } - // Create the dragged view. - CreateDraggedView(tab_data, drag_bounds); - attached_tabstrip_ = NULL; } +void TabDragController2::DetachIntoNewBrowserAndRunMoveLoop( + const gfx::Point& screen_point) { + if (GetModel(attached_tabstrip_)->count() == + static_cast<int>(drag_data_.size())) { + // All the tabs in a browser are being dragged but all the tabs weren't + // initially being dragged. For this to happen the user would have to + // start dragging a set of tabs, the other tabs close, then detach. + RunMoveLoop(); + return; + } + + // Create a new browser to house the dragged tabs and have the OS run a move + // loop. + + gfx::Point attached_point = GetAttachedDragPoint(screen_point); + + // Calculate the bounds for the tabs from the attached_tab_strip. We do this + // so that the tabs don't change size when detached. + std::vector<gfx::Rect> drag_bounds; + std::vector<BaseTab*> attached_tabs; + for (size_t i = 0; i < drag_data_.size(); ++i) + attached_tabs.push_back(drag_data_[i].attached_tab); + attached_tabstrip_->CalculateBoundsForDraggedTabs(attached_tabs, + &drag_bounds); + for (size_t i = 0; i < drag_bounds.size(); ++i) + drag_bounds[i].set_x(attached_point.x() + drag_bounds[i].x()); + + Browser* browser = CreateBrowserForDrag( + attached_tabstrip_, screen_point, &drag_bounds); + attached_tabstrip_->ReleaseDragController(); + Detach(); + BrowserView* dragged_browser_view = + BrowserView::GetBrowserViewForBrowser(browser); + dragged_browser_view->GetWidget()->SetVisibilityChangedAnimationsEnabled( + false); + Attach(static_cast<TabStrip*>(dragged_browser_view->tabstrip()), + gfx::Point()); + attached_tabstrip_->OwnDragController(this); + // TODO: come up with a cleaner way to do this. + static_cast<TabStrip*>(attached_tabstrip_)->SetTabBoundsForDrag( + drag_bounds); + + browser->window()->Show(); + browser->window()->Activate(); + dragged_browser_view->GetWidget()->SetVisibilityChangedAnimationsEnabled( + true); + RunMoveLoop(); +} + +void TabDragController2::RunMoveLoop() { + move_loop_browser_view_ = GetAttachedBrowserView(); + move_loop_browser_view_->set_move_observer(this); + is_dragging_window_ = true; + bool destroyed = false; + destroyed_ = &destroyed; + // Running the move loop release mouse capture on windows, which triggers + // destroying the drag loop. Release mouse capture ourself before this while + // the Dragcontroller isn't owned by the TabStrip. + attached_tabstrip_->ReleaseDragController(); + attached_tabstrip_->GetWidget()->ReleaseMouseCapture(); + attached_tabstrip_->OwnDragController(this); + views::Widget::MoveLoopResult result = + move_loop_browser_view_->GetWidget()->RunMoveLoop(); + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE, + content::NotificationService::AllBrowserContextsAndSources(), + content::NotificationService::NoDetails()); + + if (destroyed) + return; + destroyed_ = NULL; + move_loop_browser_view_->set_move_observer(NULL); + move_loop_browser_view_ = NULL; + is_dragging_window_ = false; + waiting_for_run_loop_to_exit_ = false; + if (end_run_loop_behavior_ == END_RUN_LOOP_CONTINUE_DRAGGING) { + end_run_loop_behavior_ = END_RUN_LOOP_STOP_DRAGGING; + if (tab_strip_to_attach_to_after_exit_) { + Detach(); + gfx::Point screen_point(GetCursorScreenPoint()); + Attach(tab_strip_to_attach_to_after_exit_, screen_point); + // Move the tabs into position. + MoveAttached(screen_point); + attached_tabstrip_->GetWidget()->Activate(); + tab_strip_to_attach_to_after_exit_ = NULL; + } + DCHECK(attached_tabstrip_); + attached_tabstrip_->GetWidget()->SetMouseCapture(attached_tabstrip_); + } else if (active_) { + EndDrag(result == views::Widget::MOVE_LOOP_CANCELED); + } +} + int TabDragController2::GetInsertionIndexForDraggedBounds( const gfx::Rect& dragged_bounds) const { + // TODO: this is wrong. It needs to calculate the bounds after adjusting for + // the tabs that will be inserted. int right_tab_x = 0; int index = -1; for (int i = 0; i < attached_tabstrip_->tab_count(); ++i) { @@ -1053,10 +1091,17 @@ std::vector<BaseTab*> TabDragController2::GetTabsMatchingDraggedContents( } void TabDragController2::EndDragImpl(EndDragType type) { + DCHECK(active_); active_ = false; bring_to_front_timer_.Stop(); + if (is_dragging_window_) { + // End the nested drag loop. + GetAttachedBrowserView()->GetWidget()->EndMoveLoop(); + waiting_for_run_loop_to_exit_ = true; + } + // Hide the current dock controllers. for (size_t i = 0; i < dock_controllers_.size(); ++i) { // Be sure and clear the controller first, that way if Hide ends up @@ -1082,12 +1127,12 @@ void TabDragController2::EndDragImpl(EndDragType type) { RevertDrag(); } // else case the only tab we were dragging was deleted. Nothing to do. - ResetDelegates(); - // Clear out drag data so we don't attempt to do anything with it. drag_data_.clear(); - source_tabstrip_->DestroyDragController(); + TabStrip* owning_tabstrip = + attached_tabstrip_ ? attached_tabstrip_ : source_tabstrip_; + owning_tabstrip->DestroyDragController(); } void TabDragController2::RevertDrag() { @@ -1100,33 +1145,16 @@ void TabDragController2::RevertDrag() { } } - bool restore_frame = attached_tabstrip_ != source_tabstrip_; if (attached_tabstrip_ && attached_tabstrip_ == source_tabstrip_) - source_tabstrip_->StoppedDraggingTabs(tabs); - - attached_tabstrip_ = source_tabstrip_; + attached_tabstrip_->StoppedDraggingTabs(tabs); - if (initial_selection_model_.empty()) { - ResetSelection(GetModel(attached_tabstrip_)); - } else { - GetModel(attached_tabstrip_)->SetSelectionFromModel( - initial_selection_model_); - } + if (initial_selection_model_.empty()) + ResetSelection(GetModel(source_tabstrip_)); + else + GetModel(source_tabstrip_)->SetSelectionFromModel(initial_selection_model_); - // If we're not attached to any TabStrip, or attached to some other TabStrip, - // we need to restore the bounds of the original TabStrip's frame, in case - // it has been hidden. - if (restore_frame) { - if (!restore_bounds_.IsEmpty()) { -#if defined(OS_WIN) && !defined(USE_AURA) - HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView(); - MoveWindow(frame_hwnd, restore_bounds_.x(), restore_bounds_.y(), - restore_bounds_.width(), restore_bounds_.height(), TRUE); -#else - NOTIMPLEMENTED(); -#endif - } - } + if (source_tabstrip_) + source_tabstrip_->GetWidget()->Activate(); } void TabDragController2::ResetSelection(TabStripModel* model) { @@ -1158,31 +1186,22 @@ void TabDragController2::RevertDragAt(size_t drag_index) { DCHECK(started_drag_); TabDragData* data = &(drag_data_[drag_index]); - if (attached_tabstrip_) { - int index = - GetModel(attached_tabstrip_)->GetIndexOfTabContents(data->contents); - if (attached_tabstrip_ != source_tabstrip_) { - // The Tab was inserted into another TabStrip. We need to put it back - // into the original one. - GetModel(attached_tabstrip_)->DetachTabContentsAt(index); - // TODO(beng): (Cleanup) seems like we should use Attach() for this - // somehow. - GetModel(source_tabstrip_)->InsertTabContentsAt( - data->source_model_index, data->contents, - (data->pinned ? TabStripModel::ADD_PINNED : 0)); - } else { - // The Tab was moved within the TabStrip where the drag was initiated. - // Move it back to the starting location. - GetModel(source_tabstrip_)->MoveTabContentsAt( - index, data->source_model_index, false); - } - } else { - // The Tab was detached from the TabStrip where the drag began, and has not - // been attached to any other TabStrip. We need to put it back into the - // source TabStrip. + int index = + GetModel(attached_tabstrip_)->GetIndexOfTabContents(data->contents); + if (attached_tabstrip_ != source_tabstrip_) { + // The Tab was inserted into another TabStrip. We need to put it back into + // the original one. + GetModel(attached_tabstrip_)->DetachTabContentsAt(index); + // TODO(beng): (Cleanup) seems like we should use Attach() for this + // somehow. GetModel(source_tabstrip_)->InsertTabContentsAt( data->source_model_index, data->contents, (data->pinned ? TabStripModel::ADD_PINNED : 0)); + } else { + // The Tab was moved within the TabStrip where the drag was initiated. Move + // it back to the starting location. + GetModel(source_tabstrip_)->MoveTabContentsAt( + index, data->source_model_index, false); } } @@ -1193,6 +1212,8 @@ void TabDragController2::CompleteDrag() { attached_tabstrip_->StoppedDraggingTabs( GetTabsMatchingDraggedContents(attached_tabstrip_)); } else { + NOTIMPLEMENTED(); + if (dock_info_.type() != DockInfo::NONE) { switch (dock_info_.type()) { case DockInfo::LEFT_OF_WINDOW: @@ -1212,23 +1233,19 @@ void TabDragController2::CompleteDrag() { break; case DockInfo::MAXIMIZE: - content::RecordAction( - UserMetricsAction("DockingWindow_Maximize")); + content::RecordAction(UserMetricsAction("DockingWindow_Maximize")); break; case DockInfo::LEFT_HALF: - content::RecordAction( - UserMetricsAction("DockingWindow_LeftHalf")); + content::RecordAction(UserMetricsAction("DockingWindow_LeftHalf")); break; case DockInfo::RIGHT_HALF: - content::RecordAction( - UserMetricsAction("DockingWindow_RightHalf")); + content::RecordAction(UserMetricsAction("DockingWindow_RightHalf")); break; case DockInfo::BOTTOM_HALF: - content::RecordAction( - UserMetricsAction("DockingWindow_BottomHalf")); + content::RecordAction(UserMetricsAction("DockingWindow_BottomHalf")); break; default: @@ -1267,50 +1284,6 @@ void TabDragController2::CompleteDrag() { ResetSelection(new_browser->tabstrip_model()); new_browser->window()->Show(); } - - CleanUpHiddenFrame(); -} - -void TabDragController2::ResetDelegates() { - for (size_t i = 0; i < drag_data_.size(); ++i) { - if (drag_data_[i].contents && - drag_data_[i].contents->tab_contents()->GetDelegate() == this) { - drag_data_[i].contents->tab_contents()->SetDelegate( - drag_data_[i].original_delegate); - } - } -} - -void TabDragController2::CreateDraggedView( - const std::vector<TabRendererData>& data, - const std::vector<gfx::Rect>& renderer_bounds) { -#if !defined(USE_AURA) - DCHECK(!view_.get()); - DCHECK_EQ(data.size(), drag_data_.size()); - - // Set up the photo booth to start capturing the contents of the dragged - // TabContents. - NativeViewPhotobooth* photobooth = - NativeViewPhotobooth::Create( - source_dragged_contents()->tab_contents()->GetNativeView()); - - gfx::Rect content_bounds; - source_dragged_contents()->tab_contents()->GetContainerBounds( - &content_bounds); - - std::vector<views::View*> renderers; - for (size_t i = 0; i < drag_data_.size(); ++i) { - BaseTab* renderer = source_tabstrip_->CreateTabForDragging(); - renderer->SetData(data[i]); - renderers.push_back(renderer); - } - // DraggedTabView takes ownership of the renderers. - view_.reset(new DraggedTabView(renderers, renderer_bounds, mouse_offset_, - content_bounds.size(), photobooth)); -#else - // TODO(beng): - NOTIMPLEMENTED(); -#endif } gfx::Point TabDragController2::GetCursorScreenPoint() const { @@ -1323,8 +1296,7 @@ gfx::Point TabDragController2::GetCursorScreenPoint() const { #endif } -gfx::Rect TabDragController2::GetViewScreenBounds( - views::View* view) const { +gfx::Rect TabDragController2::GetViewScreenBounds(views::View* view) const { gfx::Point view_topleft; views::View::ConvertPointToScreen(view, &view_topleft); gfx::Rect view_screen_bounds = view->GetLocalBounds(); @@ -1332,31 +1304,6 @@ gfx::Rect TabDragController2::GetViewScreenBounds( return view_screen_bounds; } -void TabDragController2::HideFrame() { -#if defined(OS_WIN) && !defined(USE_AURA) - // We don't actually hide the window, rather we just move it way off-screen. - // If we actually hide it, we stop receiving drag events. - HWND frame_hwnd = source_tabstrip_->GetWidget()->GetNativeView(); - RECT wr; - GetWindowRect(frame_hwnd, &wr); - MoveWindow(frame_hwnd, 0xFFFF, 0xFFFF, wr.right - wr.left, - wr.bottom - wr.top, TRUE); - - // We also save the bounds of the window prior to it being moved, so that if - // the drag session is aborted we can restore them. - restore_bounds_ = gfx::Rect(wr); -#else - NOTIMPLEMENTED(); -#endif -} - -void TabDragController2::CleanUpHiddenFrame() { - // If the model we started dragging from is now empty, we must ask the - // delegate to close the frame. - if (GetModel(source_tabstrip_)->empty()) - GetModel(source_tabstrip_)->delegate()->CloseFrameAfterDragSession(); -} - void TabDragController2::DockDisplayerDestroyed( DockDisplayer* controller) { DockWindows::iterator dock_i = @@ -1376,37 +1323,35 @@ void TabDragController2::DockDisplayerDestroyed( } void TabDragController2::BringWindowUnderMouseToFront() { - // If we're going to dock to another window, bring it to the front. gfx::NativeWindow window = dock_info_.window(); if (!window) { - gfx::NativeView dragged_view = view_->GetWidget()->GetNativeView(); + gfx::NativeView dragged_view = + attached_tabstrip_->GetWidget()->GetNativeView(); dock_windows_.insert(dragged_view); window = DockInfo::GetLocalProcessWindowAtPoint(GetCursorScreenPoint(), dock_windows_); dock_windows_.erase(dragged_view); } if (window) { -#if defined(OS_WIN) && !defined(USE_AURA) - // Move the window to the front. - SetWindowPos(window, HWND_TOP, 0, 0, 0, 0, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); - - // The previous call made the window appear on top of the dragged window, - // move the dragged window to the front. - SetWindowPos(view_->GetWidget()->GetNativeView(), HWND_TOP, 0, 0, 0, 0, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE); -#else - NOTIMPLEMENTED(); -#endif + views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window); + if (widget) { + widget->StackAtTop(); + attached_tabstrip_->GetWidget()->StackAtTop(); + } } } -TabStripModel* TabDragController2::GetModel( - TabStrip* tabstrip) const { +// static +TabStripModel* TabDragController2::GetModel(TabStrip* tabstrip) { return static_cast<BrowserTabStripController*>(tabstrip->controller())-> model(); } +BrowserView* TabDragController2::GetAttachedBrowserView() { + return BrowserView::GetBrowserViewForNativeWindow( + attached_tabstrip_->GetWidget()->GetNativeView()); +} + bool TabDragController2::AreTabsConsecutive() { for (size_t i = 1; i < drag_data_.size(); ++i) { if (drag_data_[i - 1].source_model_index + 1 != @@ -1416,3 +1361,37 @@ bool TabDragController2::AreTabsConsecutive() { } return true; } + +Browser* TabDragController2::CreateBrowserForDrag( + TabStrip* source, + const gfx::Point& screen_point, + std::vector<gfx::Rect>* drag_bounds) { + Browser* browser = Browser::Create(drag_data_[0].contents->profile()); + gfx::Point center(0, source->height() / 2); + views::View::ConvertPointToWidget(source, ¢er); + gfx::Rect new_bounds(source->GetWidget()->GetWindowScreenBounds()); + new_bounds.set_y(screen_point.y() - center.y()); + switch (GetDetachPosition(screen_point)) { + case DETACH_BEFORE: + new_bounds.set_x(screen_point.x() - center.x()); + new_bounds.Offset(-mouse_offset_.x(), 0); + break; + + case DETACH_AFTER: { + gfx::Point right_edge(source->width(), 0); + views::View::ConvertPointToWidget(source, &right_edge); + new_bounds.set_x(screen_point.x() - right_edge.x()); + new_bounds.Offset(drag_bounds->back().right() - mouse_offset_.x(), 0); + int delta = (*drag_bounds)[0].x(); + for (size_t i = 0; i < drag_bounds->size(); ++i) + (*drag_bounds)[i].Offset(-delta, 0); + break; + } + + default: + break; // Nothing to do for DETACH_ABOVE_OR_BELOW. + } + + browser->window()->SetBounds(new_bounds); + return browser; +} diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller2.h b/chrome/browser/ui/views/tabs/tab_drag_controller2.h index 33aace2..cbcbf05 100644 --- a/chrome/browser/ui/views/tabs/tab_drag_controller2.h +++ b/chrome/browser/ui/views/tabs/tab_drag_controller2.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -11,31 +11,31 @@ #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/timer.h" +#include "chrome/browser/tabs/tab_strip_model_observer.h" #include "chrome/browser/tabs/tab_strip_selection_model.h" -#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/browser/ui/tabs/dock_info.h" +#include "chrome/browser/ui/views/frame/browser_window_move_observer.h" #include "chrome/browser/ui/views/tabs/tab_drag_controller.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" -#include "content/public/browser/web_contents_delegate.h" #include "ui/gfx/rect.h" namespace views { class View; } class BaseTab; -class DraggedTabView; -class TabStrip; -class TabStripModel; - +class Browser; +class BrowserView; +class TabContentsWrapper; struct TabRendererData; +class TabStripModel; -// TabDragController implementation that creates a widget representing the -// dragged tabs when detached (dragged out of the source window). +// Implementation of TabDragController that creates a real Browser. class TabDragController2 : public TabDragController, - public content::WebContentsDelegate, public content::NotificationObserver, - public MessageLoopForUI::Observer { + public MessageLoopForUI::Observer, + public BrowserWindowMoveObserver, + public TabStripModelObserver { public: TabDragController2(); virtual ~TabDragController2(); @@ -55,9 +55,12 @@ class TabDragController2 : public TabDragController, int source_tab_offset, const TabStripSelectionModel& initial_selection_model); - // See description above fields for details on these. - bool active() const { return active_; } - const TabStrip* attached_tabstrip() const { return attached_tabstrip_; } + // Returns true if there is an active TabDragController2 that is attached to + // |tab_strip|. + static bool IsActiveAndAttachedTo(TabStrip* tab_strip); + + // Returns true if there is an active TabDragController2. + static bool IsActive(); private: class DockDisplayer; @@ -65,6 +68,15 @@ class TabDragController2 : public TabDragController, typedef std::set<gfx::NativeView> DockWindows; + // Specifies what should happen when RunMoveLoop complets. + enum EndRunLoopBehavior { + // Indicates the drag should end. + END_RUN_LOOP_STOP_DRAGGING, + + // Indicates the drag should continue. + END_RUN_LOOP_CONTINUE_DRAGGING + }; + // Enumeration of the ways a drag session can end. enum EndDragType { // Drag session exited normally: the user released the mouse. @@ -77,6 +89,13 @@ class TabDragController2 : public TabDragController, TAB_DESTROYED }; + // Enumeration of the possible positions the detached tab may detach from. + enum DetachPosition { + DETACH_BEFORE, + DETACH_AFTER, + DETACH_ABOVE_OR_BELOW + }; + // Stores the date associated with a single tab that is being dragged. struct TabDragData { TabDragData(); @@ -85,12 +104,6 @@ class TabDragController2 : public TabDragController, // The TabContentsWrapper being dragged. TabContentsWrapper* contents; - // The original content::WebContentsDelegate of |contents|, before it was - // detached from the browser window. We store this so that we can forward - // certain delegate notifications back to it if we can't handle them - // locally. - content::WebContentsDelegate* original_delegate; - // This is the index of the tab in |source_tabstrip_| when the drag // began. This is used to restore the previous state if the drag is aborted. int source_model_index; @@ -108,27 +121,11 @@ class TabDragController2 : public TabDragController, // notifications and resets the delegate of the TabContentsWrapper. void InitTabDragData(BaseTab* tab, TabDragData* drag_data); - // TabDragController overrides: + // TabDragController overrides; virtual void Drag() OVERRIDE; virtual void EndDrag(bool canceled) OVERRIDE; virtual bool GetStartedDrag() const OVERRIDE; - // Overridden from content::WebContentsDelegate: - virtual content::WebContents* OpenURLFromTab( - content::WebContents* source, - const content::OpenURLParams& params) OVERRIDE; - virtual void NavigationStateChanged(const content::WebContents* source, - unsigned changed_flags) OVERRIDE; - virtual void AddNewContents(content::WebContents* source, - content::WebContents* new_contents, - WindowOpenDisposition disposition, - const gfx::Rect& initial_pos, - bool user_gesture) OVERRIDE; - virtual void LoadingStateChanged(content::WebContents* source) OVERRIDE; - virtual bool ShouldSuppressDialogs() OVERRIDE; - virtual content::JavaScriptDialogCreator* - GetJavaScriptDialogCreator() OVERRIDE; - // Overridden from content::NotificationObserver: virtual void Observe(int type, const content::NotificationSource& source, @@ -139,14 +136,17 @@ class TabDragController2 : public TabDragController, virtual base::EventStatus WillProcessEvent( const base::NativeEvent& event) OVERRIDE; virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE; -#elif defined(TOOLKIT_USES_GTK) - virtual void WillProcessEvent(GdkEvent* event) OVERRIDE; - virtual void DidProcessEvent(GdkEvent* event) OVERRIDE; #endif + // Overriden from BrowserWindowMoveObserver: + virtual void OnWidgetMoved() OVERRIDE; + + // Overriden from TabStripModelObserver: + virtual void TabStripEmpty() OVERRIDE; + // Initialize the offset used to calculate the position to create windows // in |GetWindowCreatePoint|. This should only be invoked from |Init|. - void InitWindowCreatePoint(); + void InitWindowCreatePoint(TabStrip* tab_strip); // Returns the point where a detached window should be created given the // current mouse position. @@ -173,21 +173,24 @@ class TabDragController2 : public TabDragController, // Handles dragging tabs while the tabs are attached. void MoveAttached(const gfx::Point& screen_point); - // Handles dragging while the tabs are detached. - void MoveDetached(const gfx::Point& screen_point); + // Returns the DetachPosition given the specified location in screen + // coordinates. + DetachPosition GetDetachPosition(const gfx::Point& screen_point); + + // Returns the TabStrip for the specified window, or NULL if one doesn't exist + // or isn't appropriate. + TabStrip* GetTabStripForWindow(gfx::NativeWindow window); -#if defined(OS_WIN) && !defined(USE_AURA) // Returns the compatible TabStrip that is under the specified point (screen // coordinates), or NULL if there is none. TabStrip* GetTabStripForPoint(const gfx::Point& screen_point); -#endif DockInfo GetDockInfoAtPoint(const gfx::Point& screen_point); - // Returns the specified |tabstrip| if it contains the specified point - // (screen coordinates), NULL if it does not. - TabStrip* GetTabStripIfItContains(TabStrip* tabstrip, - const gfx::Point& screen_point) const; + // Returns true if |tabstrip| contains the specified point in screen + // coordinates. + bool DoesTabStripContain(TabStrip* tabstrip, + const gfx::Point& screen_point) const; // Attach the dragged Tab to the specified TabStrip. void Attach(TabStrip* attached_tabstrip, const gfx::Point& screen_point); @@ -195,6 +198,13 @@ class TabDragController2 : public TabDragController, // Detach the dragged Tab from the current TabStrip. void Detach(); + // Detaches the tabs being dragged, creates a new Browser to contain them and + // runs a nested move loop. + void DetachIntoNewBrowserAndRunMoveLoop(const gfx::Point& screen_point); + + // Runs a nested message loop that handles moving the current Browser. + void RunMoveLoop(); + // Returns the index where the dragged TabContents should be inserted into // |attached_tabstrip_| given the DraggedTabView's bounds |dragged_bounds| in // coordinates relative to |attached_tabstrip_| and has had the mirroring @@ -233,26 +243,12 @@ class TabDragController2 : public TabDragController, // Finishes a succesful drag operation. void CompleteDrag(); - // Resets the delegates of the TabContents. - void ResetDelegates(); - - // Create the DraggedTabView. - void CreateDraggedView(const std::vector<TabRendererData>& data, - const std::vector<gfx::Rect>& renderer_bounds); - // Utility for getting the mouse position in screen coordinates. gfx::Point GetCursorScreenPoint() const; // Returns the bounds (in screen coordinates) of the specified View. gfx::Rect GetViewScreenBounds(views::View* tabstrip) const; - // Hides the frame for the window that contains the TabStrip the current - // drag session was initiated from. - void HideFrame(); - - // Closes a hidden frame at the end of a drag session. - void CleanUpHiddenFrame(); - void DockDisplayerDestroyed(DockDisplayer* controller); void BringWindowUnderMouseToFront(); @@ -273,20 +269,30 @@ class TabDragController2 : public TabDragController, bool AreTabsConsecutive(); // Returns the TabStripModel for the specified tabstrip. - TabStripModel* GetModel(TabStrip* tabstrip) const; + static TabStripModel* GetModel(TabStrip* tabstrip); + + // Returns the BrowserView of the currently attached TabStrip. + BrowserView* GetAttachedBrowserView(); + + // Creates and returns a new Browser to handle the drag. + Browser* CreateBrowserForDrag(TabStrip* source, + const gfx::Point& screen_point, + std::vector<gfx::Rect>* drag_bounds); // Handles registering for notifications. content::NotificationRegistrar registrar_; - // The TabStrip the drag originated from. + // The TabStrip the drag originated from. This is set to NULL if the source + // tabstrip is deleted while we're detached. TabStrip* source_tabstrip_; // The TabStrip the dragged Tab is currently attached to, or NULL if the // dragged Tab is detached. TabStrip* attached_tabstrip_; - // The visual representation of the dragged Tab. - scoped_ptr<DraggedTabView> view_; + // Set to true if we've detached from a tabstrip and are running a nested + // move message loop. + bool is_dragging_window_; // The position of the mouse (in screen coordinates) at the start of the drag // operation. This is used to calculate minimum elasticity before a @@ -310,6 +316,7 @@ class TabDragController2 : public TabDragController, // is the distance of the mouse from the top left of the dragged tab as if it // were the distance of the mouse from the top left of the first tab in the // attached TabStrip from the top left of the window. + // TODO(sky): clean this up, can likely be removed. gfx::Point window_create_point_; // Location of the first tab in the source tabstrip in screen coordinates. @@ -364,6 +371,20 @@ class TabDragController2 : public TabDragController, // The selection model of |attached_tabstrip_| before the tabs were attached. TabStripSelectionModel selection_model_before_attach_; + EndRunLoopBehavior end_run_loop_behavior_; + + // If true, we're waiting for a move loop to complete. + bool waiting_for_run_loop_to_exit_; + + // The TabStrip to attach to after the move loop completes. + TabStrip* tab_strip_to_attach_to_after_exit_; + + // Non-null for the duration of RunMoveLoop. + BrowserView* move_loop_browser_view_; + + // If non-null set to true from destructor. + bool* destroyed_; + DISALLOW_COPY_AND_ASSIGN(TabDragController2); }; diff --git a/chrome/browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc b/chrome/browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc new file mode 100644 index 0000000..1cba100 --- /dev/null +++ b/chrome/browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc @@ -0,0 +1,613 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/ui/views/tabs/tab_drag_controller2.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/command_line.h" +#include "base/property_bag.h" +#include "base/string_number_conversions.h" +#include "chrome/browser/automation/ui_controls.h" +#include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" +#include "chrome/browser/ui/views/frame/browser_view.h" +#include "chrome/browser/ui/views/tabs/tab_strip.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/chrome_notification_types.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/browser/tab_contents/tab_contents.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "ui/gfx/screen.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace { + +// See comments above QuitWhenNotDragging. +class QuitDraggingObserver : public content::NotificationObserver { + public: + QuitDraggingObserver() { + registrar_.Add(this, chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE, + content::NotificationService::AllSources()); + } + + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE { + DCHECK_EQ(chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE, type); + MessageLoopForUI::current()->Quit(); + delete this; + } + + private: + virtual ~QuitDraggingObserver() {} + + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(QuitDraggingObserver); +}; + +gfx::Point GetCenterInScreenCoordinates(const views::View* view) { + gfx::Point center(view->width() / 2, view->height() / 2); + views::View::ConvertPointToScreen(view, ¢er); + return center; +} + +base::PropertyAccessor<int>* id_accessor() { + static base::PropertyAccessor<int>* accessor = NULL; + if (!accessor) + accessor = new base::PropertyAccessor<int>; + return accessor; +} + +void SetID(content::WebContents* tab_contents, int id) { + id_accessor()->SetProperty(tab_contents->GetPropertyBag(), id); +} + +void ResetIDs(TabStripModel* model, int start) { + for (int i = 0; i < model->count(); ++i) + SetID(model->GetTabContentsAt(i)->tab_contents(), start + i); +} + +std::string IDString(TabStripModel* model) { + std::string result; + for (int i = 0; i < model->count(); ++i) { + if (i != 0) + result += " "; + int* id_value = id_accessor()->GetProperty( + model->GetTabContentsAt(i)->tab_contents()->GetPropertyBag()); + if (id_value) + result += base::IntToString(*id_value); + else + result += "?"; + } + return result; +} + +// Creates a listener that quits the message loop when no longer dragging. +void QuitWhenNotDragging() { + new QuitDraggingObserver(); // QuitDraggingObserver deletes itself. +} + +} + +class TabDragController2Test : public InProcessBrowserTest { + public: + TabDragController2Test() {} + + static TabStrip* GetTabStripForBrowser(Browser* browser) { + BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser); + return static_cast<TabStrip*>(browser_view->tabstrip()); + } + + void StopAnimating(TabStrip* tab_strip) { + tab_strip->StopAnimating(true); + } + + void AddTabAndResetBrowser(Browser* browser) { + AddBlankTabAndShow(browser); + StopAnimating(GetTabStripForBrowser(browser)); + ResetIDs(browser->tabstrip_model(), 0); + } + + // Creates another window and resizes |browser()| and the new browser to + // be side by side. + Browser* CreateAnotherWindowBrowserAndRelayout() { + // Add another tab. + AddTabAndResetBrowser(browser()); + + // Create another browser. + Browser* browser2 = CreateBrowser(browser()->profile()); + ResetIDs(browser2->tabstrip_model(), 100); + + // Resize the two windows so they're right next to each other. + gfx::Rect work_area = gfx::Screen::GetMonitorWorkAreaNearestWindow( + browser()->window()->GetNativeHandle()); + gfx::Size half_size = + gfx::Size(work_area.width() / 3 - 10, work_area.height() / 2 - 10); + browser()->window()->SetBounds(gfx::Rect(work_area.origin(), half_size)); + browser2->window()->SetBounds(gfx::Rect( + work_area.x() + half_size.width(), work_area.y(), + half_size.width(), half_size.height())); + return browser2; + } + + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { + command_line->AppendSwitch(switches::kTabBrowserDragging); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TabDragController2Test); +}; + +// Creates a browser with two tabs, drags the second to the first. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DragInSameWindow) { + AddTabAndResetBrowser(browser()); + + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + TabStripModel* model = browser()->tabstrip_model(); + + gfx::Point tab_1_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(1))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_1_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::UP)); + EXPECT_EQ("1 0", IDString(model)); + EXPECT_FALSE(TabDragController2::IsActive()); + EXPECT_FALSE(tab_strip->IsDragSessionActive()); +} + +namespace { + +// Invoked from the nested message loop. +void DragToSeparateWindowStep2(TabStrip* not_attached_tab_strip, + TabStrip* target_tab_strip) { + ASSERT_FALSE(not_attached_tab_strip->IsDragSessionActive()); + ASSERT_FALSE(target_tab_strip->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + + // Drag to target_tab_strip. This should stop the nested loop from dragging + // the window. + gfx::Point target_point(target_tab_strip->width() -1, + target_tab_strip->height() / 2); + views::View::ConvertPointToScreen(target_tab_strip, &target_point); + ASSERT_TRUE(ui_controls::SendMouseMove(target_point.x(), target_point.y())); +} + +} // namespace + +// Creates two browsers, drags from first into second. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DragToSeparateWindow) { + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Create another browser. + Browser* browser2 = CreateAnotherWindowBrowserAndRelayout(); + TabStrip* tab_strip2 = GetTabStripForBrowser(browser2); + + // Move to the first tab and drag it enough so that it detaches, but not + // enough that it attaches to browser2. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&DragToSeparateWindowStep2, + tab_strip, tab_strip2))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should now be attached to tab_strip2. + ASSERT_TRUE(tab_strip2->IsDragSessionActive()); + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + + // Release the mouse, stopping the drag session. + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::UP)); + ASSERT_FALSE(tab_strip2->IsDragSessionActive()); + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + EXPECT_EQ("100 0", IDString(browser2->tabstrip_model())); + EXPECT_EQ("1", IDString(browser()->tabstrip_model())); +} + +// Drags from browser to separate window and releases mouse. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DetachToOwnWindow) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Move to the first tab and drag it enough so that it detaches. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMove( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20)); + ASSERT_TRUE(ui_controls::SendMouseEvents( + ui_controls::LEFT, ui_controls::UP)); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should no longer be dragging. + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + // There should now be another browser. + ASSERT_EQ(2u, BrowserList::size()); + Browser* new_browser = BrowserList::GetLastActive(); + ASSERT_NE(browser(), new_browser); + TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser); + ASSERT_FALSE(tab_strip2->IsDragSessionActive()); + + EXPECT_EQ("0", IDString(new_browser->tabstrip_model())); + EXPECT_EQ("1", IDString(browser()->tabstrip_model())); +} + +// Deletes a tab being dragged before the user moved enough to start a drag. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DeleteBeforeStartedDragging) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Click on the first tab, but don't move it. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + + // Should be dragging. + ASSERT_TRUE(tab_strip->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + + // Delete the tab being dragged. + delete browser()->tabstrip_model()->GetTabContentsAt(0); + + // Should have canceled dragging. + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + EXPECT_EQ("1", IDString(browser()->tabstrip_model())); +} + +// Deletes a tab being dragged while still attached. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DeleteTabWhileAttached) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Click on the first tab and move it enough so that it starts dragging but is + // still attached. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync( + gfx::Point(tab_0_center.x() + 20, tab_0_center.y()))); + + // Should be dragging. + ASSERT_TRUE(tab_strip->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + + // Delete the tab being dragged. + delete browser()->tabstrip_model()->GetTabContentsAt(0); + + // Should have canceled dragging. + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + EXPECT_EQ("1", IDString(browser()->tabstrip_model())); +} + +namespace { + +void DeleteWhileDetachedStep2(TabContentsWrapper* tab) { + delete tab; +} + +} // namespace + +// Deletes a tab being dragged after dragging a tab so that a new window is +// created. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DeleteTabWhileDetached) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Move to the first tab and drag it enough so that it detaches. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + TabContentsWrapper* to_delete = + browser()->tabstrip_model()->GetTabContentsAt(0); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&DeleteWhileDetachedStep2, to_delete))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should not be dragging. + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + EXPECT_EQ("1", IDString(browser()->tabstrip_model())); +} + +namespace { + +void DeleteSourceDetachedStep2(TabContentsWrapper* tab) { + // This ends up closing the source window. + delete tab; + // Cancel the drag. + ui_controls::SendKeyPress(NULL, ui::VKEY_ESCAPE, false, false, false, false); +} + +} // namespace + +// Detaches a tab and while detached deletes a tab from the source so that the +// source window closes then presses escape to cancel the drag. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DeleteSourceDetached) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Move to the first tab and drag it enough so that it detaches. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + TabContentsWrapper* to_delete = + browser()->tabstrip_model()->GetTabContentsAt(1); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&DeleteSourceDetachedStep2, to_delete))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should not be dragging. + Browser* new_browser = BrowserList::GetLastActive(); + ASSERT_FALSE(GetTabStripForBrowser(new_browser)->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + EXPECT_EQ("0", IDString(new_browser->tabstrip_model())); +} + +namespace { + +void PressEscapeWhileDetachedStep2() { + // Cancel the drag. + ui_controls::SendKeyPress(NULL, ui::VKEY_ESCAPE, false, false, false, false); +} + +} // namespace + +// This is disabled until NativeViewHost::Detach really detaches. +// Detaches a tab and while detached presses escape to revert the drag. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, + DISABLED_PressEscapeWhileDetached) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Move to the first tab and drag it enough so that it detaches. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&PressEscapeWhileDetachedStep2))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should not be dragging. + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + // And there should only be one window. + EXPECT_EQ(1u, BrowserList::size()); + + EXPECT_EQ("0 1", IDString(browser()->tabstrip_model())); +} + +namespace { + +void DragAllStep2() { + // Should only be one window. + ASSERT_EQ(1u, BrowserList::size()); + // Release the mouse. + ASSERT_TRUE(ui_controls::SendMouseEvents( + ui_controls::LEFT, ui_controls::UP)); +} + +} // namespace + +// Selects multiple tabs and starts dragging the window. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DragAll) { + // Add another tab. + AddTabAndResetBrowser(browser()); + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + browser()->tabstrip_model()->AddTabAtToSelection(0); + browser()->tabstrip_model()->AddTabAtToSelection(1); + + // Move to the first tab and drag it enough so that it would normally + // detach. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&DragAllStep2))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should not be dragging. + ASSERT_FALSE(tab_strip->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + + // And there should only be one window. + EXPECT_EQ(1u, BrowserList::size()); + + EXPECT_EQ("0 1", IDString(browser()->tabstrip_model())); +} + +namespace { + +// Invoked from the nested message loop. +void DragAllToSeparateWindowStep2(TabStrip* attached_tab_strip, + TabStrip* target_tab_strip) { + ASSERT_TRUE(attached_tab_strip->IsDragSessionActive()); + ASSERT_FALSE(target_tab_strip->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + ASSERT_EQ(2u, BrowserList::size()); + + // Drag to target_tab_strip. This should stop the nested loop from dragging + // the window. + gfx::Point target_point(target_tab_strip->width() - 1, + target_tab_strip->height() / 2); + views::View::ConvertPointToScreen(target_tab_strip, &target_point); + ASSERT_TRUE(ui_controls::SendMouseMove(target_point.x(), target_point.y())); +} + +} // namespace + +// Creates two browsers, selects all tabs in first and drags into second. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, DragAllToSeparateWindow) { + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Create another browser. + Browser* browser2 = CreateAnotherWindowBrowserAndRelayout(); + TabStrip* tab_strip2 = GetTabStripForBrowser(browser2); + + browser()->tabstrip_model()->AddTabAtToSelection(0); + browser()->tabstrip_model()->AddTabAtToSelection(1); + + // Move to the first tab and drag it enough so that it detaches, but not + // enough that it attaches to browser2. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&DragAllToSeparateWindowStep2, tab_strip, tab_strip2))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should now be attached to tab_strip2. + ASSERT_TRUE(tab_strip2->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + ASSERT_EQ(1u, BrowserList::size()); + + // Release the mouse, stopping the drag session. + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::UP)); + ASSERT_FALSE(tab_strip2->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + EXPECT_EQ("100 0 1", IDString(browser2->tabstrip_model())); +} + +namespace { + +// Invoked from the nested message loop. +void DragAllToSeparateWindowAndCancelStep2(TabStrip* attached_tab_strip, + TabStrip* target_tab_strip) { + ASSERT_TRUE(attached_tab_strip->IsDragSessionActive()); + ASSERT_FALSE(target_tab_strip->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + ASSERT_EQ(2u, BrowserList::size()); + + // Drag to target_tab_strip. This should stop the nested loop from dragging + // the window. + gfx::Point target_point(target_tab_strip->width() - 1, + target_tab_strip->height() / 2); + views::View::ConvertPointToScreen(target_tab_strip, &target_point); + ASSERT_TRUE(ui_controls::SendMouseMove(target_point.x(), target_point.y())); +} + +} // namespace + +// Creates two browsers, selects all tabs in first, drags into second, then hits +// escape. +IN_PROC_BROWSER_TEST_F(TabDragController2Test, + DragAllToSeparateWindowAndCancel) { + TabStrip* tab_strip = GetTabStripForBrowser(browser()); + + // Create another browser. + Browser* browser2 = CreateAnotherWindowBrowserAndRelayout(); + TabStrip* tab_strip2 = GetTabStripForBrowser(browser2); + + browser()->tabstrip_model()->AddTabAtToSelection(0); + browser()->tabstrip_model()->AddTabAtToSelection(1); + + // Move to the first tab and drag it enough so that it detaches, but not + // enough that it attaches to browser2. + gfx::Point tab_0_center( + GetCenterInScreenCoordinates(tab_strip->GetBaseTabAtModelIndex(0))); + ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_0_center)); + ASSERT_TRUE(ui_test_utils::SendMouseEventsSync( + ui_controls::LEFT, ui_controls::DOWN)); + ASSERT_TRUE(ui_controls::SendMouseMoveNotifyWhenDone( + tab_0_center.x(), tab_0_center.y() + tab_strip->height() + 20, + base::Bind(&DragAllToSeparateWindowAndCancelStep2, + tab_strip, tab_strip2))); + // Schedule observer to quit message loop when done dragging. This has to be + // async so the message loop can run. + QuitWhenNotDragging(); + MessageLoop::current()->Run(); + + // Should now be attached to tab_strip2. + ASSERT_TRUE(tab_strip2->IsDragSessionActive()); + ASSERT_TRUE(TabDragController::IsActive()); + ASSERT_EQ(1u, BrowserList::size()); + + // Cancel the drag. + ASSERT_TRUE(ui_test_utils::SendKeyPressSync( + browser2, ui::VKEY_ESCAPE, false, false, false, false)); + + ASSERT_FALSE(tab_strip2->IsDragSessionActive()); + ASSERT_FALSE(TabDragController::IsActive()); + EXPECT_EQ("100 0 1", IDString(browser2->tabstrip_model())); + + // browser() will have been destroyed, but browser2 should remain. + ASSERT_EQ(1u, BrowserList::size()); +} diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc index 53769b9..6e4d1cb 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.cc +++ b/chrome/browser/ui/views/tabs/tab_strip.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -568,19 +568,8 @@ void TabStrip::MaybeStartDrag( } void TabStrip::ContinueDrag(const views::MouseEvent& event) { - // We can get called even if |MaybeStartDrag| wasn't called in the event of - // a TabStrip animation when the mouse button is down. In this case we should - // _not_ continue the drag because it can lead to weird bugs. - if (drag_controller_.get()) { - bool started_drag = drag_controller_->GetStartedDrag(); + if (drag_controller_.get()) drag_controller_->Drag(); - if (drag_controller_->GetStartedDrag() && !started_drag) { - // The drag just started. Redirect mouse events to us to that the tab that - // originated the drag can be safely deleted. - static_cast<views::internal::RootView*>(GetWidget()->GetRootView())-> - SetMouseHandler(this); - } - } } bool TabStrip::EndDrag(bool canceled) { @@ -673,6 +662,8 @@ void TabStrip::Layout() { // Only do a layout if our size changed. if (last_layout_size_ == size()) return; + if (IsDragSessionActive()) + return; DoLayout(); } @@ -1180,9 +1171,16 @@ void TabStrip::StoppedDraggingTab(BaseTab* tab, bool* is_first_tab) { tab, new ResetDraggingStateDelegate(tab), true); } +void TabStrip::OwnDragController(TabDragController* controller) { + drag_controller_.reset(controller); +} + void TabStrip::DestroyDragController() { - if (IsDragSessionActive()) - drag_controller_.reset(NULL); + drag_controller_.reset(); +} + +TabDragController* TabStrip::ReleaseDragController() { + return drag_controller_.release(); } void TabStrip::GetDesiredTabWidths(int tab_count, @@ -1296,6 +1294,13 @@ void TabStrip::ResizeLayoutTabs() { StartResizeLayoutAnimation(); } +void TabStrip::SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds) { + StopAnimating(false); + DCHECK_EQ(tab_count(), static_cast<int>(tab_bounds.size())); + for (int i = 0; i < tab_count(); ++i) + base_tab_at_tab_index(i)->SetBoundsRect(tab_bounds[i]); +} + void TabStrip::AddMessageLoopObserver() { if (!mouse_watcher_.get()) { mouse_watcher_.reset( diff --git a/chrome/browser/ui/views/tabs/tab_strip.h b/chrome/browser/ui/views/tabs/tab_strip.h index 3913839..f6fdd17 100644 --- a/chrome/browser/ui/views/tabs/tab_strip.h +++ b/chrome/browser/ui/views/tabs/tab_strip.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -231,6 +231,8 @@ class TabStrip : public AbstractTabStripView, class RemoveTabDelegate; friend class DefaultTabDragController; + friend class TabDragController2; + friend class TabDragController2Test; // The Tabs we contain, and their last generated "good" bounds. struct TabData { @@ -335,9 +337,16 @@ class TabStrip : public AbstractTabStripView, // See description above field for details. void set_attaching_dragged_tab(bool value) { attaching_dragged_tab_ = value; } - // Destroys the active drag controller. + // Takes ownership of |controller|. + void OwnDragController(TabDragController* controller); + + // Destroys the current TabDragController. This cancel the existing drag + // operation. void DestroyDragController(); + // Releases ownership of the current TabDragController. + TabDragController* ReleaseDragController(); + // -- Tab Resize Layout ----------------------------------------------------- // Returns the exact (unrounded) current width of each tab. @@ -358,6 +367,9 @@ class TabStrip : public AbstractTabStripView, // Perform an animated resize-relayout of the TabStrip immediately. void ResizeLayoutTabs(); + // Sets the bounds of the tabs to |tab_bounds|. + void SetTabBoundsForDrag(const std::vector<gfx::Rect>& tab_bounds); + // Ensure that the message loop observer used for event spying is added and // removed appropriately so we can tell when to resize layout the tab strip. void AddMessageLoopObserver(); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 3a9ba01..531813d 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -3509,6 +3509,7 @@ 'browser/ui/views/frame/browser_view.h', 'browser/ui/views/frame/browser_view_layout.cc', 'browser/ui/views/frame/browser_view_layout.h', + 'browser/ui/views/frame/browser_window_move_observer.h', 'browser/ui/views/frame/contents_container.cc', 'browser/ui/views/frame/contents_container.h', 'browser/ui/views/frame/glass_browser_frame_view.cc', @@ -3681,6 +3682,8 @@ 'browser/ui/views/tabs/tab.h', 'browser/ui/views/tabs/tab_controller.h', 'browser/ui/views/tabs/tab_drag_controller.h', + 'browser/ui/views/tabs/tab_drag_controller2.h', + 'browser/ui/views/tabs/tab_drag_controller2.cc', 'browser/ui/views/tabs/tab_renderer_data.cc', 'browser/ui/views/tabs/tab_renderer_data.h', 'browser/ui/views/tabs/tab_strip.cc', @@ -5294,6 +5297,8 @@ ['include', '^browser/ui/views/importer/import_lock_dialog_view.cc'], ['include', '^browser/ui/views/native_constrained_window_aura.cc'], ['include', '^browser/ui/views/stubs_aura.cc'], + ['include', '^browser/ui/views/tabs/tab_drag_controller2.cc'], + ['include', '^browser/ui/views/tabs/tab_drag_controller2.h'], ], }], # Build Aura with ChromeOS. diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index bb4c46a..a0c3cb3 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -566,6 +566,7 @@ 'browser/ui/views/menu_model_adapter_test.cc', 'browser/ui/views/ssl_client_certificate_selector_browsertest.cc', 'browser/ui/views/tabs/tab_dragging_test.cc', + 'browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc', 'browser/ui/webui/workers_ui_browsertest.cc', 'test/base/chrome_test_launcher.cc', 'test/base/view_event_test_base.cc', @@ -580,6 +581,9 @@ '../build/linux/system.gyp:gtk', '../tools/xdisplaycheck/xdisplaycheck.gyp:xdisplaycheck', ], + 'sources!': [ + 'browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc', + ], }], ['toolkit_uses_gtk == 1 or chromeos==1 or (OS=="linux" and use_aura==1)', { 'dependencies': [ @@ -631,6 +635,7 @@ 'browser/ui/views/menu_item_view_test.cc', 'browser/ui/views/menu_model_adapter_test.cc', 'browser/ui/views/tabs/tab_dragging_test.cc', + 'browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc', 'test/base/view_event_test_base.cc', 'test/base/view_event_test_base.h', ], diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index a947422..71a16e3 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -870,6 +870,10 @@ enum NotificationType { // Sent when a bookmark's context menu is shown. Used to notify // tests that the context menu has been created and shown. NOTIFICATION_BOOKMARK_CONTEXT_MENU_SHOWN, + + // Notification that the nested loop using during tab dragging has returned. + // Used for testing. + NOTIFICATION_TAB_DRAG_LOOP_DONE, #endif // Sent when the tab's closeable state has changed due to increase/decrease diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc index 6935d83..6f1e7f9 100644 --- a/chrome/common/chrome_switches.cc +++ b/chrome/common/chrome_switches.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -1089,6 +1089,9 @@ const char kSyncThrowUnrecoverableError[] = "sync-throw-unrecoverable-error"; // Tries to connect to XMPP using SSLTCP first (for testing). const char kSyncTrySsltcpFirstForXmpp[] = "sync-try-ssltcp-first-for-xmpp"; +// Enables tab dragging to create a real browser. +const char kTabBrowserDragging[] = "enable-tab-browser-dragging"; + // Passes the name of the current running automated test to Chrome. const char kTestName[] = "test-name"; diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h index 6067d75..4f161bc 100644 --- a/chrome/common/chrome_switches.h +++ b/chrome/common/chrome_switches.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -296,6 +296,7 @@ extern const char kSyncNotificationHost[]; extern const char kSyncServiceURL[]; extern const char kSyncThrowUnrecoverableError[]; extern const char kSyncTrySsltcpFirstForXmpp[]; +extern const char kTabBrowserDragging[]; extern const char kTestNaClSandbox[]; extern const char kTestName[]; extern const char kTestType[]; diff --git a/chrome/test/base/ui_test_utils.cc b/chrome/test/base/ui_test_utils.cc index 5a07dce..b449669 100644 --- a/chrome/test/base/ui_test_utils.cc +++ b/chrome/test/base/ui_test_utils.cc @@ -692,6 +692,23 @@ bool SendKeyPressAndWait(const Browser* browser, return !testing::Test::HasFatalFailure(); } +bool SendMouseMoveSync(const gfx::Point& location) { + if (!ui_controls::SendMouseMoveNotifyWhenDone(location.x(), location.y(), + MessageLoop::QuitClosure())) { + return false; + } + RunMessageLoop(); + return !testing::Test::HasFatalFailure(); +} + +bool SendMouseEventsSync(ui_controls::MouseButton type, int state) { + if (!ui_controls::SendMouseEventsNotifyWhenDone( + type, state, MessageLoop::QuitClosure())) { + return false; + } + RunMessageLoop(); + return !testing::Test::HasFatalFailure(); +} TimedMessageLoopRunner::TimedMessageLoopRunner() : loop_(new MessageLoopForUI()), diff --git a/chrome/test/base/ui_test_utils.h b/chrome/test/base/ui_test_utils.h index 7cbd78a..d888b68 100644 --- a/chrome/test/base/ui_test_utils.h +++ b/chrome/test/base/ui_test_utils.h @@ -1,4 +1,4 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -17,6 +17,7 @@ #include "base/process.h" #include "base/scoped_temp_dir.h" #include "base/string16.h" +#include "chrome/browser/automation/ui_controls.h" #include "chrome/browser/ui/view_ids.h" #include "chrome/test/automation/dom_element_proxy.h" #include "content/public/browser/browser_thread.h" @@ -59,6 +60,7 @@ class WebContents; } namespace gfx { +class Point; class Size; } @@ -264,6 +266,13 @@ bool SendKeyPressAndWait(const Browser* browser, const content::NotificationSource& source) WARN_UNUSED_RESULT; +// Sends a move event blocking until received. Returns true if the event was +// successfully received. This uses ui_controls::SendMouse***NotifyWhenDone, see +// it for details. +bool SendMouseMoveSync(const gfx::Point& location) WARN_UNUSED_RESULT; +bool SendMouseEventsSync(ui_controls::MouseButton type, + int state) WARN_UNUSED_RESULT; + // Run a message loop only for the specified amount of time. class TimedMessageLoopRunner { public: |