summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-05 15:25:39 +0000
committersky@chromium.org <sky@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-01-05 15:25:39 +0000
commitc247b5158f9aaa01b256ae1e0fe4920e65b2f095 (patch)
tree2065927e8f41479ab6188cf22b558a75b40b501c
parent7e7f378a64278a0d738b631717de283ec305b02b (diff)
downloadchromium_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
-rw-r--r--chrome/app/generated_resources.grd6
-rw-r--r--chrome/browser/about_flags.cc7
-rw-r--r--chrome/browser/ui/views/frame/browser_view.cc7
-rw-r--r--chrome/browser/ui/views/frame/browser_view.h9
-rw-r--r--chrome/browser/ui/views/frame/browser_window_move_observer.h18
-rw-r--r--chrome/browser/ui/views/tabs/default_tab_drag_controller.cc59
-rw-r--r--chrome/browser/ui/views/tabs/tab_drag_controller.h5
-rw-r--r--chrome/browser/ui/views/tabs/tab_drag_controller2.cc725
-rw-r--r--chrome/browser/ui/views/tabs/tab_drag_controller2.h155
-rw-r--r--chrome/browser/ui/views/tabs/tab_drag_controller2_interactive_uitest.cc613
-rw-r--r--chrome/browser/ui/views/tabs/tab_strip.cc35
-rw-r--r--chrome/browser/ui/views/tabs/tab_strip.h16
-rw-r--r--chrome/chrome_browser.gypi5
-rw-r--r--chrome/chrome_tests.gypi5
-rw-r--r--chrome/common/chrome_notification_types.h6
-rw-r--r--chrome/common/chrome_switches.cc5
-rw-r--r--chrome/common/chrome_switches.h3
-rw-r--r--chrome/test/base/ui_test_utils.cc17
-rw-r--r--chrome/test/base/ui_test_utils.h11
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, &center);
+ 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, &center);
+ 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: