blob: ed2608c9fab5c686f99bae26d8b629012aaef508 [file] [log] [blame]
// 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_controller_interactive_uitest.h"
#include <stddef.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/cxx17_backports.h"
#include "base/dcheck_is_on.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/printing/print_preview_dialog_controller.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/browser/ui/tabs/tab_style.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/native_browser_frame_factory.h"
#include "chrome/browser/ui/views/frame/tab_strip_region_view.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/window_finder.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ui/base/window_properties.h"
#include "components/tab_groups/tab_group_color.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "components/web_modal/web_contents_modal_dialog_manager.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 "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_navigation_observer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/env.h"
#include "ui/base/test/ui_controls.h"
#include "ui/compositor/layer.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/views/controls/native/native_view_host.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window_targeter.h"
#endif
#if defined(USE_AURA) && !BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ui/views/frame/desktop_browser_frame_aura.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/public/cpp/split_view_test_api.h"
#include "ash/public/cpp/test/shell_test_api.h"
#include "ash/public/cpp/window_properties.h"
#include "ash/shell.h"
#include "ash/wm/window_state.h"
#include "base/test/simple_test_tick_clock.h"
#include "chrome/browser/ui/views/frame/browser_view_layout.h"
#include "chrome/browser/ui/views/frame/immersive_mode_controller.h"
#include "chrome/browser/ui/views/frame/immersive_mode_controller_chromeos.h"
#include "chrome/browser/ui/web_applications/test/web_app_browsertest_util.h"
#include "chrome/browser/web_applications/system_web_apps/test/test_system_web_app_installation.h"
#include "chromeos/ui/frame/immersive/immersive_fullscreen_controller_test_api.h"
#include "ui/aura/client/cursor_client.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/test/display_manager_test_api.h" // nogncheck
#include "ui/events/base_event_utils.h"
#include "ui/events/gesture_detection/gesture_configuration.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chrome/browser/ui/views/frame/desktop_browser_frame_lacros.h"
#define DESKTOP_BROWSER_FRAME_AURA DesktopBrowserFrameLacros
#elif defined(OS_LINUX)
#include "chrome/browser/ui/views/frame/desktop_browser_frame_aura_linux.h"
#define DESKTOP_BROWSER_FRAME_AURA DesktopBrowserFrameAuraLinux
#else
#define DESKTOP_BROWSER_FRAME_AURA DesktopBrowserFrameAura
#endif
#if defined(OS_WIN)
#include "ui/base/ui_base_features.h"
#endif
using content::WebContents;
using display::Display;
using ui_test_utils::GetDisplays;
namespace test {
namespace {
const char kTabDragControllerInteractiveUITestUserDataKey[] =
"TabDragControllerInteractiveUITestUserData";
class TabDragControllerInteractiveUITestUserData
: public base::SupportsUserData::Data {
public:
explicit TabDragControllerInteractiveUITestUserData(int id) : id_(id) {}
~TabDragControllerInteractiveUITestUserData() override {}
int id() { return id_; }
private:
int id_;
};
#if BUILDFLAG(IS_CHROMEOS_ASH)
aura::Window* GetWindowForTabStrip(TabStrip* tab_strip) {
return tab_strip ? tab_strip->GetWidget()->GetNativeWindow() : nullptr;
}
#endif
gfx::Point GetLeftCenterInScreenCoordinates(const views::View* view) {
gfx::Point center = view->GetLocalBounds().CenterPoint();
center.set_x(center.x() - view->GetLocalBounds().width() / 4);
views::View::ConvertPointToScreen(view, &center);
return center;
}
gfx::Point GetRightCenterInScreenCoordinates(const views::View* view) {
gfx::Point center = view->GetLocalBounds().CenterPoint();
center.set_x(center.x() + view->GetLocalBounds().width() / 4);
views::View::ConvertPointToScreen(view, &center);
return center;
}
} // namespace
class QuitDraggingObserver {
public:
explicit QuitDraggingObserver(TabStrip* tab_strip) {
tab_strip->GetDragContext()->SetDragControllerCallbackForTesting(
base::BindOnce(&QuitDraggingObserver::OnDragControllerSet,
weak_ptr_factory_.GetWeakPtr()));
}
QuitDraggingObserver(const QuitDraggingObserver&) = delete;
QuitDraggingObserver& operator=(const QuitDraggingObserver&) = delete;
~QuitDraggingObserver() = default;
// The observer should be constructed prior to initiating the drag. To prevent
// misuse via constructing a temporary object, Wait is marked lvalue-only.
void Wait() & { run_loop_.Run(); }
private:
void OnDragControllerSet(TabDragController* controller) {
controller->SetDragLoopDoneCallbackForTesting(base::BindOnce(
&QuitDraggingObserver::Quit, weak_ptr_factory_.GetWeakPtr()));
}
void Quit() { run_loop_.QuitWhenIdle(); }
base::RunLoop run_loop_;
base::WeakPtrFactory<QuitDraggingObserver> weak_ptr_factory_{this};
};
void SetID(WebContents* web_contents, int id) {
web_contents->SetUserData(
&kTabDragControllerInteractiveUITestUserDataKey,
std::make_unique<TabDragControllerInteractiveUITestUserData>(id));
}
void ResetIDs(TabStripModel* model, int start) {
for (int i = 0; i < model->count(); ++i)
SetID(model->GetWebContentsAt(i), start + i);
}
std::string IDString(TabStripModel* model) {
std::string result;
for (int i = 0; i < model->count(); ++i) {
if (i != 0)
result += " ";
WebContents* contents = model->GetWebContentsAt(i);
TabDragControllerInteractiveUITestUserData* user_data =
static_cast<TabDragControllerInteractiveUITestUserData*>(
contents->GetUserData(
&kTabDragControllerInteractiveUITestUserDataKey));
if (user_data)
result += base::NumberToString(user_data->id());
else
result += "?";
}
return result;
}
TabStrip* GetTabStripForBrowser(Browser* browser) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
return browser_view->tabstrip();
}
TabDragController* GetTabDragController(TabStrip* tab_strip) {
return tab_strip->GetDragContext()->GetDragController();
}
} // namespace test
using test::GetTabDragController;
using test::GetTabStripForBrowser;
using test::IDString;
using test::ResetIDs;
using test::SetID;
using ui_test_utils::GetCenterInScreenCoordinates;
TabDragControllerTest::TabDragControllerTest()
: browser_list(BrowserList::GetInstance()) {}
TabDragControllerTest::~TabDragControllerTest() {
}
void TabDragControllerTest::StopAnimating(TabStrip* tab_strip) {
tab_strip->StopAnimating(true);
}
void TabDragControllerTest::AddTabsAndResetBrowser(Browser* browser,
int additional_tabs,
const GURL& url) {
for (int i = 0; i < additional_tabs; i++) {
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
chrome::AddSelectedTabWithURL(browser, url,
ui::PAGE_TRANSITION_AUTO_TOPLEVEL);
observer.Wait();
}
browser->window()->Show();
StopAnimating(GetTabStripForBrowser(browser));
// Perform any scheduled layouts so the tabstrip is in a steady state.
BrowserView::GetBrowserViewForBrowser(browser)
->GetWidget()
->LayoutRootViewIfNecessary();
ResetIDs(browser->tab_strip_model(), 0);
}
Browser* TabDragControllerTest::CreateAnotherBrowserAndResize() {
Browser* browser2 = CreateBrowser(browser()->profile());
ResetIDs(browser2->tab_strip_model(), 100);
Resize(browser(), browser2);
return browser2;
}
void TabDragControllerTest::Resize(Browser* browser1, Browser* browser2) {
// Resize the two windows so they're right next to each other.
const gfx::NativeWindow window = browser1->window()->GetNativeWindow();
gfx::Rect work_area =
display::Screen::GetScreen()->GetDisplayNearestWindow(window).work_area();
const gfx::Size size(work_area.width() / 3, work_area.height() / 2);
gfx::Rect browser_rect(work_area.origin(), size);
browser1->window()->SetBounds(browser_rect);
browser_rect.set_x(browser_rect.right());
browser2->window()->SetBounds(browser_rect);
}
void TabDragControllerTest::SetWindowFinderForTabStrip(
TabStrip* tab_strip,
std::unique_ptr<WindowFinder> window_finder) {
ASSERT_TRUE(GetTabDragController(tab_strip));
GetTabDragController(tab_strip)->window_finder_ = std::move(window_finder);
}
void TabDragControllerTest::HandleGestureEvent(TabStrip* tab_strip,
ui::GestureEvent* event) {
tab_strip->OnGestureEvent(event);
}
bool TabDragControllerTest::HasDragStarted(TabStrip* tab_strip) const {
return GetTabDragController(tab_strip) &&
GetTabDragController(tab_strip)->started_drag();
}
void TabDragControllerTest::SetUp() {
#if defined(USE_AURA)
// This needs to be disabled as it can interfere with when events are
// processed. In particular if input throttling is turned on, then when an
// event ack runs the event may not have been processed.
aura::Env::set_initial_throttle_input_on_resize_for_testing(false);
#endif
InProcessBrowserTest::SetUp();
}
namespace {
enum InputSource {
INPUT_SOURCE_MOUSE = 0,
INPUT_SOURCE_TOUCH = 1
};
int GetDetachY(TabStrip* tab_strip) {
return std::max(TabDragController::kTouchVerticalDetachMagnetism,
TabDragController::kVerticalDetachMagnetism) +
tab_strip->height() + 1;
}
bool GetIsDragged(Browser* browser) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
return ash::WindowState::Get(browser->window()->GetNativeWindow())
->is_dragged();
#endif
return false;
}
} // namespace
#if !BUILDFLAG(IS_CHROMEOS_ASH) && defined(USE_AURA)
// Following classes verify a crash scenario. Specifically on Windows when focus
// changes it can trigger capture being lost. This was causing a crash in tab
// dragging as it wasn't set up to handle this scenario. These classes
// synthesize this scenario.
// Allows making ClearNativeFocus() invoke ReleaseCapture().
class TestDesktopBrowserFrameAura : public DESKTOP_BROWSER_FRAME_AURA {
public:
TestDesktopBrowserFrameAura(BrowserFrame* browser_frame,
BrowserView* browser_view)
: DESKTOP_BROWSER_FRAME_AURA(browser_frame, browser_view),
release_capture_(false) {}
TestDesktopBrowserFrameAura(const TestDesktopBrowserFrameAura&) = delete;
TestDesktopBrowserFrameAura& operator=(const TestDesktopBrowserFrameAura&) =
delete;
~TestDesktopBrowserFrameAura() override = default;
void ReleaseCaptureOnNextClear() {
release_capture_ = true;
}
void ClearNativeFocus() override {
views::DesktopNativeWidgetAura::ClearNativeFocus();
if (release_capture_) {
release_capture_ = false;
GetWidget()->ReleaseCapture();
}
}
private:
// If true ReleaseCapture() is invoked in ClearNativeFocus().
bool release_capture_;
};
// Factory for creating a TestDesktopBrowserFrameAura.
class TestNativeBrowserFrameFactory : public NativeBrowserFrameFactory {
public:
TestNativeBrowserFrameFactory() = default;
TestNativeBrowserFrameFactory(const TestNativeBrowserFrameFactory&) = delete;
TestNativeBrowserFrameFactory& operator=(
const TestNativeBrowserFrameFactory&) = delete;
~TestNativeBrowserFrameFactory() override = default;
NativeBrowserFrame* Create(BrowserFrame* browser_frame,
BrowserView* browser_view) override {
return new TestDesktopBrowserFrameAura(browser_frame, browser_view);
}
};
class TabDragCaptureLostTest : public TabDragControllerTest {
public:
TabDragCaptureLostTest() {
NativeBrowserFrameFactory::Set(new TestNativeBrowserFrameFactory);
}
TabDragCaptureLostTest(const TabDragCaptureLostTest&) = delete;
TabDragCaptureLostTest& operator=(const TabDragCaptureLostTest&) = delete;
};
// See description above for details.
IN_PROC_BROWSER_TEST_F(TabDragCaptureLostTest, ReleaseCaptureOnDrag) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
gfx::Point tab_1_center(GetCenterInScreenCoordinates(tab_strip->tab_at(1)));
ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(tab_1_center) &&
ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN));
TestDesktopBrowserFrameAura* frame =
static_cast<TestDesktopBrowserFrameAura*>(
BrowserView::GetBrowserViewForBrowser(browser())->GetWidget()->
native_widget_private());
// Invoke ReleaseCaptureOnDrag() so that when the drag happens and focus
// changes capture is released and the drag cancels.
frame->ReleaseCaptureOnNextClear();
ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(
GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
}
IN_PROC_BROWSER_TEST_F(TabDragControllerTest, GestureEndShouldEndDragTest) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
Tab* tab1 = tab_strip->tab_at(1);
gfx::Point tab_1_center(tab1->width() / 2, tab1->height() / 2);
ui::GestureEvent gesture_tap_down(
tab_1_center.x(), tab_1_center.x(), 0, base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_TAP_DOWN));
tab_strip->MaybeStartDrag(tab1, gesture_tap_down,
tab_strip->GetSelectionModel());
EXPECT_TRUE(TabDragController::IsActive());
ui::GestureEvent gesture_end(tab_1_center.x(), tab_1_center.x(), 0,
base::TimeTicks(),
ui::GestureEventDetails(ui::ET_GESTURE_END));
HandleGestureEvent(tab_strip, &gesture_end);
EXPECT_FALSE(TabDragController::IsActive());
EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
}
#endif
class DetachToBrowserTabDragControllerTest
: public TabDragControllerTest,
public ::testing::WithParamInterface<const char*> {
public:
DetachToBrowserTabDragControllerTest() {
scoped_feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{
features::kWebUITabStrip,
#if defined(OS_WIN)
// Disable NativeWinOcclusion to avoid it interfering with test
// for dragging over occluded browser window.
features::kCalculateNativeWinOcclusion,
#endif // OS_WIN
});
}
DetachToBrowserTabDragControllerTest(
const DetachToBrowserTabDragControllerTest&) = delete;
DetachToBrowserTabDragControllerTest& operator=(
const DetachToBrowserTabDragControllerTest&) = delete;
void SetUpOnMainThread() override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
root_ = browser()->window()->GetNativeWindow()->GetRootWindow();
// Disable flings which might otherwise inadvertently be generated from
// tests' touch events.
ui::GestureConfiguration::GetInstance()->set_min_fling_velocity(
std::numeric_limits<float>::max());
#endif
#if defined(OS_MAC)
// Currently MacViews' browser windows are shown in the background and could
// be obscured by other windows if there are any. This should be fixed in
// order to be consistent with other platforms.
EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
#endif // OS_MAC
}
InputSource input_source() const {
return strstr(GetParam(), "mouse") ?
INPUT_SOURCE_MOUSE : INPUT_SOURCE_TOUCH;
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
bool SendTouchEventsSync(int action, int id, const gfx::Point& location) {
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
if (!ui_controls::SendTouchEventsNotifyWhenDone(
action, id, location.x(), location.y(), run_loop.QuitClosure())) {
return false;
}
run_loop.Run();
return true;
}
#endif
// The following methods update one of the mouse or touch input depending upon
// the InputSource.
bool PressInput(const gfx::Point& location, int id = 0) {
if (input_source() == INPUT_SOURCE_MOUSE) {
return ui_test_utils::SendMouseMoveSync(location) &&
ui_test_utils::SendMouseEventsSync(
ui_controls::LEFT, ui_controls::DOWN);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
return SendTouchEventsSync(ui_controls::PRESS, id, location);
#else
NOTREACHED();
return false;
#endif
}
bool DragInputTo(const gfx::Point& location) {
if (input_source() == INPUT_SOURCE_MOUSE)
return ui_test_utils::SendMouseMoveSync(location);
#if BUILDFLAG(IS_CHROMEOS_ASH)
return SendTouchEventsSync(ui_controls::MOVE, 0, location);
#else
NOTREACHED();
return false;
#endif
}
bool DragInputToAsync(const gfx::Point& location) {
if (input_source() == INPUT_SOURCE_MOUSE)
return ui_controls::SendMouseMove(location.x(), location.y());
#if BUILDFLAG(IS_CHROMEOS_ASH)
return ui_controls::SendTouchEvents(ui_controls::MOVE, 0, location.x(),
location.y());
#else
NOTREACHED();
return false;
#endif
}
bool DragInputToNotifyWhenDone(const gfx::Point& location,
base::OnceClosure task) {
if (input_source() == INPUT_SOURCE_MOUSE) {
return ui_controls::SendMouseMoveNotifyWhenDone(
location.x(), location.y(), std::move(task));
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
return ui_controls::SendTouchEventsNotifyWhenDone(
ui_controls::MOVE, 0, location.x(), location.y(), std::move(task));
#else
NOTREACHED();
return false;
#endif
}
bool ReleaseInput(int id = 0, bool async = false) {
if (input_source() == INPUT_SOURCE_MOUSE) {
return async ? ui_controls::SendMouseEvents(ui_controls::LEFT,
ui_controls::UP)
: ui_test_utils::SendMouseEventsSync(ui_controls::LEFT,
ui_controls::UP);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
return async ? ui_controls::SendTouchEvents(ui_controls::RELEASE, id, 0, 0)
: SendTouchEventsSync(ui_controls::RELEASE, id, gfx::Point());
#else
NOTREACHED();
return false;
#endif
}
void ReleaseInputAfterWindowDetached(int first_dragged_tab_width) {
// On macOS, we want to avoid generating the input event [which requires an
// associated window] until the window has been detached. Failure to do so
// causes odd behavior [e.g. on macOS 10.10, the mouse-up will reactivate
// the first window].
if (browser_list->size() != 2u) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&DetachToBrowserTabDragControllerTest::
ReleaseInputAfterWindowDetached,
base::Unretained(this), first_dragged_tab_width),
base::TimeDelta::FromMilliseconds(1));
return;
}
// The tab getting dragged into the new browser should have the same width
// as before it was dragged.
EXPECT_EQ(first_dragged_tab_width,
GetTabStripForBrowser(browser_list->get(1))->tab_at(0)->width());
// Windows hangs if you use a sync mouse event here.
ASSERT_TRUE(ReleaseInput(0, true));
}
bool MoveInputTo(const gfx::Point& location) {
aura::Env::GetInstance()->SetLastMouseLocation(location);
if (input_source() == INPUT_SOURCE_MOUSE)
return ui_test_utils::SendMouseMoveSync(location);
#if BUILDFLAG(IS_CHROMEOS_ASH)
return SendTouchEventsSync(ui_controls::MOVE, 0, location);
#else
NOTREACHED();
return false;
#endif
}
void AddBlankTabAndShow(Browser* browser) {
InProcessBrowserTest::AddBlankTabAndShow(browser);
}
// Returns true if the tab dragging info is correctly set on the attached
// browser window. On Chrome OS, the info includes two window properties.
bool IsTabDraggingInfoSet(TabStrip* attached_tabstrip,
TabStrip* source_tabstrip) {
DCHECK(attached_tabstrip);
#if BUILDFLAG(IS_CHROMEOS_ASH)
aura::Window* dragged_window =
test::GetWindowForTabStrip(attached_tabstrip);
attached_tabstrip->GetWidget()->GetNativeWindow();
aura::Window* source_window =
(source_tabstrip && source_tabstrip != attached_tabstrip)
? test::GetWindowForTabStrip(source_tabstrip)
: nullptr;
return dragged_window->GetProperty(ash::kIsDraggingTabsKey) &&
dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey) ==
source_window;
#else
return true;
#endif
}
// Returns true if the tab dragging info is correctly cleared on the attached
// browser window.
bool IsTabDraggingInfoCleared(TabStrip* attached_tabstrip) {
DCHECK(attached_tabstrip);
#if BUILDFLAG(IS_CHROMEOS_ASH)
aura::Window* dragged_window =
test::GetWindowForTabStrip(attached_tabstrip);
return !dragged_window->GetProperty(ash::kIsDraggingTabsKey) &&
!dragged_window->GetProperty(ash::kTabDraggingSourceWindowKey);
#else
return true;
#endif
}
void DragTabAndNotify(TabStrip* tab_strip,
base::OnceClosure task,
int tab = 0,
int drag_x_offset = 0) {
test::QuitDraggingObserver observer(tab_strip);
// Move to the tab and drag it enough so that it detaches.
const gfx::Point tab_0_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(tab));
ASSERT_TRUE(PressInput(tab_0_center));
ASSERT_TRUE(DragInputToNotifyWhenDone(
tab_0_center + gfx::Vector2d(drag_x_offset, GetDetachY(tab_strip)),
std::move(task)));
observer.Wait();
}
void DragToDetachGroupAndNotify(TabStrip* tab_strip,
base::OnceClosure task,
tab_groups::TabGroupId group,
int drag_x_offset = 0) {
test::QuitDraggingObserver observer(tab_strip);
// Move to the tab and drag it enough so that it detaches.
const gfx::Point group_header_center =
GetCenterInScreenCoordinates(tab_strip->group_header(group));
ASSERT_TRUE(PressInput(group_header_center));
ASSERT_TRUE(DragInputToNotifyWhenDone(
group_header_center +
gfx::Vector2d(drag_x_offset, GetDetachY(tab_strip)),
std::move(task)));
observer.Wait();
}
// Helper method to click the first tab. Used to ensure no additional widgets
// are in focus. For example, the tab editor bubble is automatically opened
// upon creating a new group.
void EnsureFocusToTabStrip(TabStrip* tab_strip) {
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
}
Browser* browser() const { return InProcessBrowserTest::browser(); }
private:
#if BUILDFLAG(IS_CHROMEOS_ASH)
// The root window for the event generator.
aura::Window* root_ = nullptr;
#endif
base::test::ScopedFeatureList scoped_feature_list_;
absl::optional<web_app::AppId> tabbed_app_id_;
};
// Creates a browser with four tabs. The first three belong in the same Tab
// Group. Dragging the third tab to after the fourth tab will result in a
// removal of the dragged tab from its group. Then dragging the second tab to
// after the third tab will also result in a removal of that dragged tab from
// its group.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragRightToUngroupTab) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({0, 1, 2});
StopAnimating(tab_strip);
EXPECT_EQ(4, model->count());
EXPECT_EQ(3u, model->group_model()->GetTabGroup(group)->ListTabs().length());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Dragging the tab in the second index to the tab in the third index switches
// the tabs and removes the dragged tab from the group.
EXPECT_EQ("0 1 3 2", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 2));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(2));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(3));
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Dragging the tab in the first index to the tab in the second index (tab 3)
// switches the tabs and removes the dragged tab from the group.
EXPECT_EQ("0 3 1 2", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 1));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(1));
}
// Creates a browser with four tabs. The last three belong in the same Tab
// Group. Dragging the second tab to before the first tab will result in a
// removal of the dragged tab from its group. Then dragging the third tab to
// before the second tab will also result in a removal of that dragged tab from
// its group.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragLeftToUngroupTab) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({1, 2, 3});
StopAnimating(tab_strip);
EXPECT_EQ(4, model->count());
EXPECT_EQ(3u, model->group_model()->GetTabGroup(group)->ListTabs().length());
EXPECT_EQ(group, model->GetTabGroupForTab(1).value());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Dragging the tab in the first index to the tab in the zero-th index
// switches the tabs and removes the dragged tab from the group.
EXPECT_EQ("1 0 2 3", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(2, 4));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(0));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(1));
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Dragging the tab in the second index to the tab in the first index switches
// the tabs and removes the dragged tab from the group.
EXPECT_EQ("1 2 0 3", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(3, 4));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(2));
}
// Creates a browser with four tabs. The first three belong in the same Tab
// Group. Dragging tabs in a tab group within the defined threshold does not
// modify the group of the dragged tab.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragTabWithinGroupDoesNotModifyGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({0, 1, 2});
StopAnimating(tab_strip);
EXPECT_EQ(4, model->count());
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 3));
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Dragging the tab in the zero-th index to the tab in the first index
// switches the tabs but group membership does not change.
EXPECT_EQ("1 0 2 3", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 3));
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Dragging the tab in the second index to the tab in the first index switches
// the tabs but group membership does not change.
EXPECT_EQ("1 2 0 3", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 3));
}
// Creates a browser with four tabs. The first tab is in a Tab Group. Dragging
// the only tab in that group will remove the group.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragOnlyTabInGroupRemovesGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({0});
StopAnimating(tab_strip);
EXPECT_EQ(4, model->count());
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 1));
// Dragging the tab in the zero-th index to the tab in the first index
// switches the tabs and removes the group of the zero-th tab.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 0 2 3", IDString(model));
EXPECT_FALSE(model->group_model()->ContainsTabGroup(group));
}
// Creates a browser with four tabs. The first tab is in Tab Group 1. The
// third tab is in Tab Group 2. Dragging the second tab over one to the left
// will result in the second tab joining Tab Group 1. Then dragging the third
// tab over one to the left will result in the tab joining Tab Group 1. Then
// dragging the fourth tab over one to the left will result in the tab joining
// Tab Group 1 as well.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragSingleTabLeftIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({0});
model->AddToNewGroup({2});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
// Dragging the tab in the first index toward the tab in the zero-th index
// switches the tabs and adds the dragged tab to the group.
// Drag only half the tab length, to ensure that the tab in the first index
// lands in the slot between the tab in the zero-th index and the group
// header to the left of the zero-th index.
const gfx::Point drag_origin =
GetCenterInScreenCoordinates(tab_strip->tab_at(1));
const gfx::Vector2d drag_amount =
gfx::Vector2d(-tab_strip->tab_at(0)->width() / 2, 0);
ASSERT_TRUE(PressInput(drag_origin));
ASSERT_TRUE(DragInputTo(drag_origin + drag_amount));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 0 2 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 2));
// Dragging the tab in the second index to the tab in the first index switches
// the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 2 0 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 3));
// Dragging the tab in the third index to the tab in the second index switches
// the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 2 3 0", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 4));
}
// Creates a browser with four tabs. The last tab is in Tab Group 1. The
// second tab is in Tab Group 2. Dragging the third tab over one to the right
// will result in the tab joining Tab Group 1. Then dragging the second tab
// over one to the right will result in the tab joining Tab Group 1. Then
// dragging the first tab over one to the right will result in the tab joining
// Tab Group 1 as well.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragSingleTabRightIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({3});
model->AddToNewGroup({1});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
// Dragging the tab in the second index to the tab in the third index switches
// the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("0 1 3 2", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(2, 4));
// Dragging the tab in the first index to the tab in the second index switches
// the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("0 3 1 2", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(1, 4));
// Dragging the tab in the zero-th index to the tab in the first index
// switches the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("3 0 1 2", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 4));
}
// Creates a browser with four tabs each in its own group. Selecting and
// dragging the first and third tabs right at the first tab will result in the
// tabs joining the same group as the tab in the second position. Then dragging
// the tabs over two to the right will result in the tabs joining the same group
// as the last tab.
// Flaky on windows. http://crbug.com/1164561
#if defined(OS_WIN)
#define MAYBE_DragMultipleTabsRightIntoGroup \
DISABLED_DragMultipleTabsRightIntoGroup
#else
#define MAYBE_DragMultipleTabsRightIntoGroup DragMultipleTabsRightIntoGroup
#endif
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragMultipleTabsRightIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
model->AddToNewGroup({0});
tab_groups::TabGroupId group2 = model->AddToNewGroup({1});
model->AddToNewGroup({2});
tab_groups::TabGroupId group4 = model->AddToNewGroup({3});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
// Click the first tab and select third tab so both first and third tabs are
// selected.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
browser()->tab_strip_model()->ToggleSelectionAt(2);
// Dragging the first tab at the leftmost position to the center of the first
// tab will result in the tabs joining the beginning of Tab Group 2.
const gfx::Point center_first_tab =
GetCenterInScreenCoordinates(tab_strip->tab_at(0));
ASSERT_TRUE(
PressInput(test::GetLeftCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(center_first_tab));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("0 2 1 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group4)->ListTabs(), gfx::Range(3, 4));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(0, 3));
// Dragging the center of the first tab to the center of the third tab will
// result in the tabs joining the end of Tab Group 4.
const gfx::Point left_center_third_tab =
test::GetLeftCenterInScreenCoordinates(tab_strip->tab_at(2));
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(left_center_third_tab));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 3 0 2", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(0, 1));
EXPECT_EQ(group_model->GetTabGroup(group4)->ListTabs(), gfx::Range(1, 4));
}
// Creates a browser with four tabs each in its own group. Selecting and
// dragging the second and fourth tabs left at the fourth tab will result in the
// tabs joining the same group as the tab in the third position.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragMultipleTabsLeftIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({0});
model->AddToNewGroup({1});
tab_groups::TabGroupId group3 = model->AddToNewGroup({2});
model->AddToNewGroup({3});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
// Click the second tab and select fourth tab so both second and fourth tabs
// are selected.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
browser()->tab_strip_model()->ToggleSelectionAt(3);
// Dragging the fourth tab slightly to the left will result in the two
// selected tabs joining the end of Tab Group 3.
const gfx::Point left_center_fourth_tab =
test::GetLeftCenterInScreenCoordinates(tab_strip->tab_at(3));
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(DragInputTo(left_center_fourth_tab));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("0 2 1 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 1));
EXPECT_EQ(group_model->GetTabGroup(group3)->ListTabs(), gfx::Range(1, 4));
}
// Creates a browser with four tabs. The first two tabs are in Tab Group 1.
// Dragging the third tab over one to the left will result in the tab joining
// Tab Group 1. While this drag is still in session, pressing escape will revert
// group of the tab to before the drag session started.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertDragSingleTabIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({0, 1});
StopAnimating(tab_strip);
// Dragging the tab in the second index to the tab in the first index switches
// the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
EXPECT_EQ("0 2 1 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 3));
ASSERT_TRUE(TabDragController::IsActive());
// Pressing escape will revert the tabs to original state before the drag.
ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_ESCAPE, false,
false, false, false));
EXPECT_EQ("0 1 2 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 2));
}
// Creates a browser with four tabs. The last two tabs are in Tab Group 1. The
// second tab is in Tab Group 2. Dragging the second tab over one to the right
// will result in the tab joining Tab Group 1. While this drag is still in
// session, pressing escape will revert group of the tab to before the drag
// session started.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertDragSingleTabGroupIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({2, 3});
tab_groups::TabGroupId group2 = model->AddToNewGroup({1});
const tab_groups::TabGroupVisualData new_data(
u"Foo", tab_groups::TabGroupColorId::kCyan);
group_model->GetTabGroup(group2)->SetVisualData(new_data);
StopAnimating(tab_strip);
// Dragging the tab in the first index to the tab in the second index switches
// the tabs and adds the dragged tab to the group.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
EXPECT_EQ("0 2 1 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(1, 4));
ASSERT_TRUE(TabDragController::IsActive());
// Pressing escape will revert the tabs to original state before the drag.
ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_ESCAPE, false,
false, false, false));
EXPECT_EQ("0 1 2 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(2, 4));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(1, 2));
const tab_groups::TabGroupVisualData* group2_visual_data =
group_model->GetTabGroup(group2)->visual_data();
EXPECT_THAT(group2_visual_data->title(), new_data.title());
EXPECT_THAT(group2_visual_data->color(), new_data.color());
}
// Creates a browser with four tabs. The middle two belong in the same Tab
// Group. Dragging the group header will result in both the grouped tabs moving
// together.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragGroupHeaderDragsGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({1, 2});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
ASSERT_EQ(4, model->count());
ASSERT_EQ(2u, group_model->GetTabGroup(group)->ListTabs().length());
// Drag the entire group right by its header.
ASSERT_TRUE(
PressInput(GetCenterInScreenCoordinates(tab_strip->group_header(group))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("0 3 1 2", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group)->ListTabs(), gfx::Range(2, 4));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(0));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(1));
// Drag the entire group left by its header.
ASSERT_TRUE(
PressInput(GetCenterInScreenCoordinates(tab_strip->group_header(group))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 2 0 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group)->ListTabs(), gfx::Range(0, 2));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(2));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(3));
}
// Creates a browser with four tabs. The first two belong in Tab Group 1, and
// the last two belong in Tab Group 2. Dragging the group header of Tab Group 1
// right will result in Tab Group 1 moving but avoiding Tab Group 2.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragGroupHeaderRightAvoidsOtherGroups) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({0, 1});
tab_groups::TabGroupId group2 = model->AddToNewGroup({2, 3});
StopAnimating(tab_strip);
ASSERT_EQ(4, model->count());
ASSERT_EQ(2u, group_model->GetTabGroup(group1)->ListTabs().length());
ASSERT_EQ(2u, group_model->GetTabGroup(group2)->ListTabs().length());
// Drag group1 right, but not far enough to get to the other side of group2.
ASSERT_TRUE(PressInput(
GetCenterInScreenCoordinates(tab_strip->group_header(group1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Expect group1 to "snap back" to its current position, avoiding group2.
EXPECT_EQ("0 1 2 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 2));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(2, 4));
// Drag group1 right, far enough to get to the other side of group2.
ASSERT_TRUE(PressInput(
GetCenterInScreenCoordinates(tab_strip->group_header(group1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Expect group1 to "snap to" the other side of group2 and not land in the
// middle.
EXPECT_EQ("2 3 0 1", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(2, 4));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(0, 2));
}
// Creates a browser with four tabs. The first two belong in Tab Group 1, and
// the last two belong in Tab Group 2. Dragging the group header of Tab Group 2
// left will result in Tab Group 2 moving but avoiding Tab Group 1.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragGroupHeaderLeftAvoidsOtherGroups) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group1 = model->AddToNewGroup({0, 1});
tab_groups::TabGroupId group2 = model->AddToNewGroup({2, 3});
StopAnimating(tab_strip);
ASSERT_EQ(4, model->count());
ASSERT_EQ(2u, group_model->GetTabGroup(group1)->ListTabs().length());
ASSERT_EQ(2u, group_model->GetTabGroup(group2)->ListTabs().length());
// Drag group2 left, but not far enough to get to the other side of group1.
ASSERT_TRUE(PressInput(
GetCenterInScreenCoordinates(tab_strip->group_header(group2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Expect group2 to "snap back" to its current position, avoiding group1.
EXPECT_EQ("0 1 2 3", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(0, 2));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(2, 4));
// Drag group2 left, far enough to get to the other side of group1.
ASSERT_TRUE(PressInput(
GetCenterInScreenCoordinates(tab_strip->group_header(group2))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// Expect group2 to "snap to" the other side of group1 and not land in the
// middle.
EXPECT_EQ("2 3 0 1", IDString(model));
EXPECT_EQ(group_model->GetTabGroup(group1)->ListTabs(), gfx::Range(2, 4));
EXPECT_EQ(group_model->GetTabGroup(group2)->ListTabs(), gfx::Range(0, 2));
}
// Creates a browser with four tabs. The first tab is pinned, and the last
// three belong in the same Tab Group. Dragging the pinned tab to the middle
// of the group will not result in the pinned tab getting grouped or moving
// into the group.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragPinnedTabDoesNotGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
model->SetTabPinned(0, true);
tab_groups::TabGroupId group = model->AddToNewGroup({1, 2, 3});
StopAnimating(tab_strip);
ASSERT_EQ(4, model->count());
ASSERT_EQ(3u, model->group_model()->GetTabGroup(group)->ListTabs().length());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
// The pinned tab should not have moved or joined the group.
EXPECT_EQ("0 1 2 3", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(1, 4));
EXPECT_EQ(absl::nullopt, model->GetTabGroupForTab(0));
}
// Drags a tab within the window (without dragging the whole window) then
// pressing a key ends the drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
KeyPressShouldEndDragTest) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_TRUE(ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_TAB, false,
false, false, false));
StopAnimating(tab_strip);
EXPECT_EQ("1 0", IDString(browser()->tab_strip_model()));
EXPECT_FALSE(TabDragController::IsActive());
EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
}
#if defined(USE_AURA)
bool SubtreeShouldBeExplored(aura::Window* window,
const gfx::Point& local_point) {
gfx::Point point_in_parent = local_point;
aura::Window::ConvertPointToTarget(window, window->parent(),
&point_in_parent);
gfx::Point point_in_root = local_point;
aura::Window::ConvertPointToTarget(window, window->GetRootWindow(),
&point_in_root);
ui::MouseEvent event(ui::ET_MOUSE_MOVED, point_in_parent, point_in_root,
base::TimeTicks::Now(), 0, 0);
return window->targeter()->SubtreeShouldBeExploredForEvent(window, event);
}
// The logic to find the target tabstrip should take the window mask into
// account. This test hangs without the fix. crbug.com/473080.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragWithMaskedWindows) {
AddTabsAndResetBrowser(browser(), 1);
aura::Window* browser_window = browser()->window()->GetNativeWindow();
const gfx::Rect bounds = browser_window->GetBoundsInScreen();
aura::test::TestWindowDelegate masked_window_delegate;
masked_window_delegate.set_can_focus(false);
std::unique_ptr<aura::Window> masked_window(
aura::test::CreateTestWindowWithDelegate(
&masked_window_delegate, 10, bounds, browser_window->parent()));
masked_window->SetProperty(aura::client::kZOrderingKey,
ui::ZOrderLevel::kFloatingWindow);
auto targeter = std::make_unique<aura::WindowTargeter>();
targeter->SetInsets(gfx::Insets(0, bounds.width() - 10, 0, 0));
masked_window->SetEventTargeter(std::move(targeter));
ASSERT_FALSE(SubtreeShouldBeExplored(masked_window.get(),
gfx::Point(bounds.width() - 11, 0)));
ASSERT_TRUE(SubtreeShouldBeExplored(masked_window.get(),
gfx::Point(bounds.width() - 9, 0)));
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 0", IDString(model));
EXPECT_FALSE(TabDragController::IsActive());
EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
}
#endif // USE_AURA
namespace {
// Invoked from the nested run loop.
void DragToSeparateWindowStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_FALSE(not_attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Test that after the tabs are detached from the source tabstrip (in this
// case |not_attached_tab_strip|), the tab dragging info should be properly
// cleared on the source tabstrip.
EXPECT_TRUE(test->IsTabDraggingInfoCleared(not_attached_tab_strip));
// At this moment there should be a new browser window for the dragged tabs.
EXPECT_EQ(3u, test->browser_list->size());
Browser* new_browser = test->browser_list->get(2);
TabStrip* new_tab_strip = GetTabStripForBrowser(new_browser);
ASSERT_TRUE(new_tab_strip->GetDragContext()->IsDragSessionActive());
// Test that the tab dragging info should be correctly set on the new window.
EXPECT_TRUE(
test->IsTabDraggingInfoSet(new_tab_strip, not_attached_tab_strip));
// Drag to target_tab_strip. This should stop the nested loop from dragging
// the window.
//
// Note: It's possible on small screens for the windows to overlap, so we want
// to pick a point squarely within the second tab strip and not in the
// starting window.
const gfx::Rect old_window_bounds =
not_attached_tab_strip->GetWidget()->GetWindowBoundsInScreen();
const gfx::Rect target_bounds = target_tab_strip->GetBoundsInScreen();
gfx::Point target_point = target_bounds.CenterPoint();
target_point.set_x(std::max(target_point.x(), old_window_bounds.right() + 1));
ASSERT_TRUE(target_bounds.Contains(target_point));
ASSERT_TRUE(test->DragInputToAsync(target_point));
}
} // namespace
// Flaky. https://crbug.com/1176998
#if (defined(OS_MAC) && defined(ARCH_CPU_ARM64)) || defined(OS_LINUX)
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
#define MAYBE_DragToSeparateWindow DISABLED_DragToSeparateWindow
#define MAYBE_DragToSeparateWindowWithModalDialog \
DISABLED_DragToSeparateWindowWithModalDialog
#else
#define MAYBE_DragToSeparateWindow DragToSeparateWindow
#define MAYBE_DragToSeparateWindowWithModalDialog \
DragToSeparateWindowWithModalDialog
#endif
// Creates two browsers, drags from first into second.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragToSeparateWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
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.
DragTabAndNotify(tab_strip, base::BindOnce(&DragToSeparateWindowStep2, this,
tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
EXPECT_FALSE(GetIsDragged(browser()));
EXPECT_TRUE(IsTabDraggingInfoCleared(tab_strip));
EXPECT_TRUE(IsTabDraggingInfoSet(tab_strip2, tab_strip));
// Drag to the trailing end of the tabstrip to ensure we're in a predictable
// spot within the strip.
StopAnimating(tab_strip2);
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip2->tab_at(1))));
// Release mouse or touch, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_TRUE(IsTabDraggingInfoCleared(tab_strip2));
EXPECT_EQ("100 0", IDString(browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser2));
// Both windows should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_FALSE(browser2->window()->IsMaximized());
// The tab strip should no longer have capture because the drag was ended and
// mouse/touch was released.
EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
EXPECT_FALSE(tab_strip2->GetWidget()->HasCapture());
}
// Creates two browsers, drags a tab with an attached tab modal dialog from the
// first browser into the second.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragToSeparateWindowWithModalDialog) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
EXPECT_EQ(2, tab_strip->GetTabCount());
EXPECT_TRUE(tab_strip->IsActiveTab(tab_strip->tab_at(1)));
content::WebContents* active_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Open a print preview dialog.
content::TestNavigationObserver observer(nullptr);
observer.StartWatchingNewWebContents();
chrome::Print(browser());
observer.Wait();
observer.StopWatchingNewWebContents();
// Check the print dialog is active and focused.
web_modal::WebContentsModalDialogManager* manager =
web_modal::WebContentsModalDialogManager::FromWebContents(
active_contents);
ASSERT_TRUE(manager && manager->IsDialogActive());
gfx::NativeWindow dialog_window =
web_modal::WebContentsModalDialogManager::TestApi(manager)
.GetTopmostDialog();
views::Widget* dialog_widget =
views::Widget::GetWidgetForNativeWindow(dialog_window);
views::View* focused_view =
dialog_widget->GetFocusManager()->GetFocusedView();
EXPECT_TRUE(focused_view && focused_view->GetWidget() == dialog_widget);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
EXPECT_EQ(1, tab_strip2->GetTabCount());
// Drag the active tab to browser2.
DragTabAndNotify(
tab_strip,
base::BindOnce(&DragToSeparateWindowStep2, this, tab_strip, tab_strip2),
1);
ASSERT_TRUE(ReleaseInput());
// Confirm the tab is moved to browser2.
EXPECT_EQ(1, tab_strip->GetTabCount());
EXPECT_EQ(2, tab_strip2->GetTabCount());
#if defined(OS_MAC)
// Wait for the widget to be activated since activation is asynchronous.
views::test::WidgetActivationWaiter waiter(dialog_widget, true);
waiter.Wait();
#else
// On Aura, the active window is the top level widget as it redirects the
// events to the dialog.
views::Widget* top_widget = dialog_widget->GetTopLevelWidget();
ASSERT_TRUE(top_widget);
EXPECT_TRUE(top_widget->IsActive());
EXPECT_EQ(dialog_widget->GetFocusManager(), top_widget->GetFocusManager());
#endif
// The dialog should be focused after the tab drag.
EXPECT_EQ(focused_view, dialog_widget->GetFocusManager()->GetFocusedView());
}
#if defined(OS_WIN)
// Create two browsers, with the second one occluded, and drag from first over
// second. This should create a third browser, w/o bringing forward the second
// browser, because it's occluded.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragToOccludedWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
// Mark the second brower as occluded. NativeWindow occlusion calculation has
// been disabled in test constructor, so we don't need an actual occluding
// window.
browser2->window()
->GetNativeWindow()
->GetHost()
->SetNativeWindowOcclusionState(aura::Window::OcclusionState::OCCLUDED);
// Drag a tab from first browser to middle of first tab of the second,
// occluded browser, and drop. This should create a third browser window.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(
DragInputToAsync(GetCenterInScreenCoordinates(tab_strip2->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
EXPECT_EQ(3u, browser_list->size());
}
#endif // OS_WIN
namespace {
// WindowFinder that calls OnMouseCaptureLost() from
// GetLocalProcessWindowAtPoint().
class CaptureLoseWindowFinder : public WindowFinder {
public:
explicit CaptureLoseWindowFinder(TabStrip* tab_strip)
: tab_strip_(tab_strip) {}
CaptureLoseWindowFinder(const CaptureLoseWindowFinder&) = delete;
CaptureLoseWindowFinder& operator=(const CaptureLoseWindowFinder&) = delete;
~CaptureLoseWindowFinder() override = default;
// WindowFinder:
gfx::NativeWindow GetLocalProcessWindowAtPoint(
const gfx::Point& screen_point,
const std::set<gfx::NativeWindow>& ignore) override {
static_cast<views::View*>(tab_strip_)->OnMouseCaptureLost();
return nullptr;
}
private:
TabStrip* tab_strip_;
};
} // namespace
// Calls OnMouseCaptureLost() from WindowFinder::GetLocalProcessWindowAtPoint()
// and verifies we don't crash. This simulates a crash seen on windows.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
CaptureLostDuringDrag) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Press on first tab so drag is active. Reset WindowFinder to one that causes
// capture to be lost from within GetLocalProcessWindowAtPoint(), then
// continue drag. The capture lost should trigger the drag to cancel.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(tab_strip->GetDragContext()->IsDragSessionActive());
SetWindowFinderForTabStrip(
tab_strip, base::WrapUnique(new CaptureLoseWindowFinder(tab_strip)));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
}
namespace {
#if BUILDFLAG(IS_CHROMEOS_ASH)
bool IsWindowPositionManaged(aura::Window* window) {
return window->GetProperty(ash::kWindowPositionManagedTypeKey);
}
bool HasUserChangedWindowPositionOrSize(aura::Window* window) {
return ash::WindowState::Get(window)->bounds_changed_by_user();
}
#else
bool IsWindowPositionManaged(gfx::NativeWindow window) {
return true;
}
bool HasUserChangedWindowPositionOrSize(gfx::NativeWindow window) {
return false;
}
#endif
// Encapsulates waiting for the browser window to become maximized. This is
// needed for example on Chrome desktop linux, where window maximization is done
// asynchronously as an event received from a different process.
class MaximizedBrowserWindowWaiter {
public:
explicit MaximizedBrowserWindowWaiter(BrowserWindow* window)
: window_(window) {}
MaximizedBrowserWindowWaiter(const MaximizedBrowserWindowWaiter&) = delete;
MaximizedBrowserWindowWaiter& operator=(const MaximizedBrowserWindowWaiter&) =
delete;
~MaximizedBrowserWindowWaiter() = default;
// Blocks until the browser window becomes maximized.
void Wait() {
if (CheckMaximized())
return;
base::RunLoop run_loop;
quit_ = run_loop.QuitClosure();
run_loop.Run();
}
private:
bool CheckMaximized() {
if (!window_->IsMaximized()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&MaximizedBrowserWindowWaiter::CheckMaximized),
base::Unretained(this)));
return false;
}
// Quit the run_loop to end the wait.
if (!quit_.is_null())
std::move(quit_).Run();
return true;
}
// The browser window observed by this waiter.
BrowserWindow* window_;
// The waiter's RunLoop quit closure.
base::RepeatingClosure quit_;
};
} // namespace
// Drags from browser to separate window and releases mouse.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DetachToOwnWindow) {
const gfx::Rect initial_bounds(browser()->window()->GetBounds());
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
int tab_0_width = tab_strip->tab_at(0)->width();
DragTabAndNotify(tab_strip,
base::BindOnce(&DetachToBrowserTabDragControllerTest::
ReleaseInputAfterWindowDetached,
base::Unretained(this), tab_0_width));
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should now be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
EXPECT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
EXPECT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
EXPECT_EQ("0", IDString(new_browser->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// The bounds of the initial window should not have changed.
EXPECT_EQ(initial_bounds.ToString(),
browser()->window()->GetBounds().ToString());
EXPECT_FALSE(GetIsDragged(browser()));
EXPECT_FALSE(GetIsDragged(new_browser));
// After this both windows should still be manageable.
EXPECT_TRUE(IsWindowPositionManaged(browser()->window()->GetNativeWindow()));
EXPECT_TRUE(IsWindowPositionManaged(
new_browser->window()->GetNativeWindow()));
// Both windows should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_FALSE(new_browser->window()->IsMaximized());
// The tab strip should no longer have capture because the drag was ended and
// mouse/touch was released.
EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
EXPECT_FALSE(tab_strip2->GetWidget()->HasCapture());
}
// Tests that a tab can be dragged from a browser window that is resized to full
// screen.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DetachFromFullsizeWindow) {
// Resize the browser window so that it is as big as the work area.
gfx::Rect work_area =
display::Screen::GetScreen()
->GetDisplayNearestWindow(browser()->window()->GetNativeWindow())
.work_area();
browser()->window()->SetBounds(work_area);
const gfx::Rect initial_bounds(browser()->window()->GetBounds());
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
int tab_0_width = tab_strip->tab_at(0)->width();
DragTabAndNotify(tab_strip,
base::BindOnce(&DetachToBrowserTabDragControllerTest::
ReleaseInputAfterWindowDetached,
base::Unretained(this), tab_0_width));
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should now be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
EXPECT_EQ("0", IDString(new_browser->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// The bounds of the initial window should not have changed.
EXPECT_EQ(initial_bounds.ToString(),
browser()->window()->GetBounds().ToString());
EXPECT_FALSE(GetIsDragged(browser()));
EXPECT_FALSE(GetIsDragged(new_browser));
// After this both windows should still be manageable.
EXPECT_TRUE(IsWindowPositionManaged(browser()->window()->GetNativeWindow()));
EXPECT_TRUE(
IsWindowPositionManaged(new_browser->window()->GetNativeWindow()));
// The tab strip should no longer have capture because the drag was ended and
// mouse/touch was released.
EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
EXPECT_FALSE(tab_strip2->GetWidget()->HasCapture());
}
// This test doesn't make sense on Mac, since it has no concept of "maximized".
#if !defined(OS_MAC)
// Drags from browser to a separate window and releases mouse.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DetachToOwnWindowFromMaximizedWindow) {
// Maximize the initial browser window.
browser()->window()->Maximize();
MaximizedBrowserWindowWaiter(browser()->window()).Wait();
ASSERT_TRUE(browser()->window()->IsMaximized());
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
int tab_0_width = tab_strip->tab_at(0)->width();
DragTabAndNotify(tab_strip,
base::BindOnce(&DetachToBrowserTabDragControllerTest::
ReleaseInputAfterWindowDetached,
base::Unretained(this), tab_0_width));
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should now be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
EXPECT_EQ("0", IDString(new_browser->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// The bounds of the initial window should not have changed.
EXPECT_TRUE(browser()->window()->IsMaximized());
EXPECT_FALSE(GetIsDragged(browser()));
EXPECT_FALSE(GetIsDragged(new_browser));
// After this both windows should still be manageable.
EXPECT_TRUE(IsWindowPositionManaged(browser()->window()->GetNativeWindow()));
EXPECT_TRUE(IsWindowPositionManaged(
new_browser->window()->GetNativeWindow()));
// The new window should be maximized.
MaximizedBrowserWindowWaiter(new_browser->window()).Wait();
EXPECT_TRUE(new_browser->window()->IsMaximized());
}
#endif
#if BUILDFLAG(IS_CHROMEOS_ASH)
// This test makes sense only on Chrome OS where we have the immersive
// fullscreen mode. The detached tab to a new browser window should remain in
// immersive fullscreen mode.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DetachToOwnWindowWhileInImmersiveFullscreenMode) {
// Toggle the immersive fullscreen mode for the initial browser.
chrome::ToggleFullscreenMode(browser());
ImmersiveModeController* controller =
BrowserView::GetBrowserViewForBrowser(browser())
->immersive_mode_controller();
ASSERT_TRUE(controller->IsEnabled());
// Forcively reveal the tabstrip immediately.
std::unique_ptr<ImmersiveRevealedLock> lock(
controller->GetRevealedLock(ImmersiveModeController::ANIMATE_REVEAL_NO));
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
int tab_0_width = tab_strip->tab_at(0)->width();
DragTabAndNotify(tab_strip,
base::BindOnce(&DetachToBrowserTabDragControllerTest::
ReleaseInputAfterWindowDetached,
base::Unretained(this), tab_0_width));
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should now be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
EXPECT_EQ("0", IDString(new_browser->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// The bounds of the initial window should not have changed.
EXPECT_TRUE(browser()->window()->IsFullscreen());
ASSERT_TRUE(BrowserView::GetBrowserViewForBrowser(browser())
->immersive_mode_controller()
->IsEnabled());
EXPECT_FALSE(GetIsDragged(browser()));
EXPECT_FALSE(GetIsDragged(new_browser));
// After this both windows should still be manageable.
EXPECT_TRUE(IsWindowPositionManaged(browser()->window()->GetNativeWindow()));
EXPECT_TRUE(
IsWindowPositionManaged(new_browser->window()->GetNativeWindow()));
// The new browser should be in immersive fullscreen mode.
ASSERT_TRUE(BrowserView::GetBrowserViewForBrowser(new_browser)
->immersive_mode_controller()
->IsEnabled());
EXPECT_TRUE(new_browser->window()->IsFullscreen());
}
#endif
// Deletes a tab being dragged before the user moved enough to start a drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DeleteBeforeStartedDragging) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Click on the first tab, but don't move it.
gfx::Point tab_0_center(GetCenterInScreenCoordinates(tab_strip->tab_at(0)));
ASSERT_TRUE(PressInput(tab_0_center));
// Should be dragging.
ASSERT_TRUE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Delete the tab being dragged.
browser()->tab_strip_model()->DetachWebContentsAtForInsertion(0);
// Should have canceled dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser()));
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragDoesntStartFromClick) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Click on the first tab, but don't move it.
gfx::Point tab_0_center(GetCenterInScreenCoordinates(tab_strip->tab_at(0)));
EXPECT_TRUE(PressInput(tab_0_center));
// A drag session should exist, but the drag should not have started.
EXPECT_TRUE(tab_strip->GetDragContext()->IsDragSessionActive());
EXPECT_TRUE(TabDragController::IsActive());
EXPECT_FALSE(HasDragStarted(tab_strip));
// Move the mouse enough to start the drag. It doesn't matter whether this
// is enough to create a window or not.
EXPECT_TRUE(DragInputTo(gfx::Point(tab_0_center.x() + 20, tab_0_center.y())));
// Drag should now have started.
EXPECT_TRUE(HasDragStarted(tab_strip));
EXPECT_TRUE(ReleaseInput());
}
// Deletes a tab being dragged while still attached.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DeleteTabWhileAttached) {
AddTabsAndResetBrowser(browser(), 1);
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->tab_at(0)));
ASSERT_TRUE(PressInput(tab_0_center));
ASSERT_TRUE(DragInputTo(
gfx::Point(tab_0_center.x() + 20, tab_0_center.y())));
// Should be dragging.
ASSERT_TRUE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Delete the tab being dragged.
browser()->tab_strip_model()->DetachWebContentsAtForInsertion(0);
// Should have canceled dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser()));
}
namespace {
void CloseTabsWhileDetachedStep2(const BrowserList* browser_list) {
ASSERT_EQ(2u, browser_list->size());
Browser* old_browser = browser_list->get(0);
EXPECT_EQ("0 3", IDString(old_browser->tab_strip_model()));
Browser* new_browser = browser_list->get(1);
EXPECT_EQ("1 2", IDString(new_browser->tab_strip_model()));
chrome::CloseTab(new_browser);
}
} // namespace
// Selects 2 tabs out of 4, drags them out and closes the new browser window
// while dragging tabs.
#if (defined(OS_WIN) || defined(OS_MAC))
// TODO(crbug.com/1031801) Test is flaky on Windows and Mac.
#define MAYBE_DeleteTabsWhileDetached DISABLED_DeleteTabsWhileDetached
#else
#define MAYBE_DeleteTabsWhileDetached DeleteTabsWhileDetached
#endif
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DeleteTabsWhileDetached) {
AddTabsAndResetBrowser(browser(), 3);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
EXPECT_EQ("0 1 2 3", IDString(browser()->tab_strip_model()));
// Click the first tab and select two middle tabs.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(ReleaseInput());
browser()->tab_strip_model()->ToggleSelectionAt(2);
// Press mouse button in the second tab and drag it enough to detach.
DragTabAndNotify(
tab_strip, base::BindOnce(&CloseTabsWhileDetachedStep2, browser_list), 2);
// Should not be dragging.
ASSERT_EQ(1u, browser_list->size());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// Both tabs "1" and "2" get closed.
EXPECT_EQ("0 3", IDString(browser()->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser()));
}
namespace {
void PressEscapeWhileDetachedStep2(const BrowserList* browser_list) {
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
new_browser->window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
false, false));
}
} // namespace
// Detaches a tab and while detached presses escape to revert the drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertDragWhileDetached) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
DragTabAndNotify(
tab_strip, base::BindOnce(&PressEscapeWhileDetachedStep2, browser_list));
// Should not be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// And there should only be one window.
EXPECT_EQ(1u, browser_list->size());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
// Remaining browser window should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
// The tab strip should no longer have capture because the drag was ended and
// mouse/touch was released.
EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
}
// Creates a browser with two tabs, drags the second to the first.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest, DragInSameWindow) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
// Test that the dragging info is correctly set on |tab_strip|.
EXPECT_TRUE(IsTabDraggingInfoSet(tab_strip, tab_strip));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 0", IDString(model));
EXPECT_FALSE(TabDragController::IsActive());
EXPECT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
// Test that the dragging info is properly cleared after dragging.
EXPECT_TRUE(IsTabDraggingInfoCleared(tab_strip));
// The tab strip should no longer have capture because the drag was ended and
// mouse/touch was released.
EXPECT_FALSE(tab_strip->GetWidget()->HasCapture());
}
namespace {
void DragAllStep2(DetachToBrowserTabDragControllerTest* test,
const BrowserList* browser_list) {
// Should only be one window.
ASSERT_EQ(1u, browser_list->size());
// Windows hangs if you use a sync mouse event here.
ASSERT_TRUE(test->ReleaseInput(0, true));
}
} // namespace
// Selects multiple tabs and starts dragging the window.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest, DragAll) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
browser()->tab_strip_model()->ToggleSelectionAt(0);
const gfx::Rect initial_bounds = browser()->window()->GetBounds();
// Move to the first tab and drag it enough so that it would normally
// detach.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragAllStep2, this, browser_list));
// Should not be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// And there should only be one window.
EXPECT_EQ(1u, browser_list->size());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser()));
// Remaining browser window should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
const gfx::Rect final_bounds = browser()->window()->GetBounds();
// Size unchanged, but it should have moved down.
EXPECT_EQ(initial_bounds.size(), final_bounds.size());
EXPECT_EQ(initial_bounds.origin().x(), final_bounds.origin().x());
EXPECT_EQ(initial_bounds.origin().y() + GetDetachY(tab_strip),
final_bounds.origin().y());
}
namespace {
// Invoked from the nested run loop.
void DragAllToSeparateWindowStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_TRUE(attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, test->browser_list->size());
// Drag to target_tab_strip. This should stop the nested loop from dragging
// the window.
ASSERT_TRUE(
test->DragInputToAsync(GetCenterInScreenCoordinates(target_tab_strip)));
}
} // namespace
// Flaky. http://crbug.com/1128774 and http://crbug.com/1176998
#if defined(OS_MAC) || defined(OS_WIN) || defined(OS_LINUX)
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
// These were flaking on all macs, so commented out ARCH_ above for
// crbug.com/1160917 too.
#define MAYBE_DragAllToSeparateWindow DISABLED_DragAllToSeparateWindow
#else
#define MAYBE_DragAllToSeparateWindow DragAllToSeparateWindow
#endif
// Creates two browsers, selects all tabs in first and drags into second.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragAllToSeparateWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
browser()->tab_strip_model()->ToggleSelectionAt(0);
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip, base::BindOnce(&DragAllToSeparateWindowStep2,
this, tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(1u, browser_list->size());
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("100 0 1", IDString(browser2->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser2));
// Remaining browser window should not be maximized
EXPECT_FALSE(browser2->window()->IsMaximized());
}
#if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
#define MAYBE_DragWindowIntoGroup DISABLED_DragWindowIntoGroup
#else
#define MAYBE_DragWindowIntoGroup DragWindowIntoGroup
#endif
// Creates two browser with two tabs each. The first browser has one tab in a
// group and the second tab not in a group. The second browser {browser2} has
// two tabs in another group {group1}. Dragging the two tabs in the first
// browser into the middle of the second browser will insert the two dragged
// tabs into the {group1}} after the first tab.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragWindowIntoGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 1);
model->AddToNewGroup({0});
StopAnimating(tab_strip);
// Set up the second browser with two tabs in a group with distinct IDs.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
TabStripModel* model2 = browser2->tab_strip_model();
AddTabsAndResetBrowser(browser2, 1);
ResetIDs(model2, 100);
tab_groups::TabGroupId group1 = model2->AddToNewGroup({0, 1});
StopAnimating(tab_strip2);
// Click the first tab and select the second tab so they are the only ones
// selected.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
browser()->tab_strip_model()->ToggleSelectionAt(1);
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip, base::BindOnce(&DragAllToSeparateWindowStep2,
this, tab_strip, tab_strip2));
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
EXPECT_EQ("100 0 1 101", IDString(model2));
EXPECT_EQ(model2->group_model()->GetTabGroup(group1)->ListTabs(),
gfx::Range(0, 4));
}
#if defined(OS_MAC) || defined(OS_LINUX)
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
// Flaky on Mac10.14 and Linux: https://crbug.com/1213345
#define MAYBE_DragGroupHeaderToSeparateWindow \
DISABLED_DragGroupHeaderToSeparateWindow
#else
#define MAYBE_DragGroupHeaderToSeparateWindow DragGroupHeaderToSeparateWindow
#endif
// Creates two browsers, then drags a group from one to the other.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragGroupHeaderToSeparateWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 1);
tab_groups::TabGroupId group = model->AddToNewGroup({0, 1});
tab_groups::TabGroupColorId group_color = tab_strip->GetGroupColorId(group);
StopAnimating(tab_strip);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
TabStripModel* model2 = browser2->tab_strip_model();
StopAnimating(tab_strip2);
// Drag the group by its header into the second browser.
DragToDetachGroupAndNotify(tab_strip,
base::BindOnce(&DragAllToSeparateWindowStep2, this,
tab_strip, tab_strip2),
group);
ASSERT_TRUE(ReleaseInput());
// Expect the group to be in browser2, but with a new tab_groups::TabGroupId.
EXPECT_EQ("100 0 1", IDString(model2));
std::vector<tab_groups::TabGroupId> groups2 =
model2->group_model()->ListTabGroups();
EXPECT_EQ(1u, groups2.size());
EXPECT_EQ(model2->group_model()->GetTabGroup(groups2[0])->ListTabs(),
gfx::Range(1, 3));
EXPECT_EQ(groups2[0], group);
EXPECT_EQ(tab_strip2->GetGroupColorId(groups2[0]), group_color);
}
// Drags a tab group by the header to a new position toward the right and
// presses escape to revert the drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertHeaderDragRight) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({0, 1});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
TabGroupHeader* group_header = tab_strip->group_header(group);
EXPECT_FALSE(group_header->dragging());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(group_header)));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(2))));
ASSERT_TRUE(group_header->dragging());
ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
browser()->window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
false, false));
EXPECT_EQ(1u, browser_list->size());
EXPECT_FALSE(group_header->dragging());
EXPECT_EQ("0 1 2 3", IDString(browser()->tab_strip_model()));
std::vector<tab_groups::TabGroupId> groups =
model->group_model()->ListTabGroups();
EXPECT_EQ(1u, groups.size());
EXPECT_EQ(model->group_model()->GetTabGroup(groups[0])->ListTabs(),
gfx::Range(0, 2));
}
// Drags a tab group by the header to a new position toward the left and presses
// escape to revert the drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertHeaderDragLeft) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({2, 3});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
TabGroupHeader* group_header = tab_strip->group_header(group);
EXPECT_FALSE(group_header->dragging());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(group_header)));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(group_header->dragging());
ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
browser()->window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
false, false));
EXPECT_EQ(1u, browser_list->size());
EXPECT_FALSE(group_header->dragging());
EXPECT_EQ("0 1 2 3", IDString(browser()->tab_strip_model()));
std::vector<tab_groups::TabGroupId> groups =
model->group_model()->ListTabGroups();
EXPECT_EQ(1u, groups.size());
EXPECT_EQ(model->group_model()->GetTabGroup(groups[0])->ListTabs(),
gfx::Range(2, 4));
}
namespace {
void PressEscapeWhileDetachedHeaderStep2(
DetachToBrowserTabDragControllerTest* test) {
// At this moment there should be a new browser window for the dragged tabs.
EXPECT_EQ(2u, test->browser_list->size());
Browser* new_browser = test->browser_list->get(1);
std::vector<tab_groups::TabGroupId> new_browser_groups =
new_browser->tab_strip_model()->group_model()->ListTabGroups();
EXPECT_EQ(1u, new_browser_groups.size());
EXPECT_EQ(0u, test->browser()
->tab_strip_model()
->group_model()
->ListTabGroups()
.size());
TabGroupHeader* new_group_header =
GetTabStripForBrowser(new_browser)->group_header(new_browser_groups[0]);
EXPECT_TRUE(new_group_header->dragging());
ASSERT_TRUE(ui_test_utils::SendKeyPressToWindowSync(
new_browser->window()->GetNativeWindow(), ui::VKEY_ESCAPE, false, false,
false, false));
}
} // namespace
// Drags a tab group by the header and while detached presses escape to revert
// the drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertHeaderDragWhileDetached) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 1);
tab_groups::TabGroupId group = model->AddToNewGroup({0});
StopAnimating(tab_strip);
EnsureFocusToTabStrip(tab_strip);
DragToDetachGroupAndNotify(
tab_strip, base::BindOnce(&PressEscapeWhileDetachedHeaderStep2, this),
group);
EXPECT_FALSE(tab_strip->group_header(group)->dragging());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(1u, browser_list->size());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
std::vector<tab_groups::TabGroupId> groups =
model->group_model()->ListTabGroups();
EXPECT_EQ(1u, groups.size());
EXPECT_EQ(model->group_model()->GetTabGroup(groups[0])->ListTabs(),
gfx::Range(0, 1));
}
// Creates a browser with four tabs where the second and third tab is in a
// collapsed group. Drag the fourth tab to the left past the group header. The
// fourth tab should swap places with the collapsed group header.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragTabLeftPastCollapsedGroupHeader) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({1, 2});
StopAnimating(tab_strip);
EXPECT_EQ(4, model->count());
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(1, 3));
EXPECT_FALSE(model->IsGroupCollapsed(group));
tab_strip->controller()->ToggleTabGroupCollapsedState(group);
StopAnimating(tab_strip);
EXPECT_TRUE(model->IsGroupCollapsed(group));
// Dragging the last tab to the left should cause it to swap places with the
// collapsed group header.
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(DragInputTo(
test::GetLeftCenterInScreenCoordinates(tab_strip->tab_at(3))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("0 3 1 2", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(2, 4));
EXPECT_TRUE(model->IsGroupCollapsed(group));
}
// Creates a browser with four tabs where the second and third tab is in a
// collapsed group. Drag the first tab to the right past the group header. The
// first tab should swap places with the collapsed group header.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragTabRightPastCollapsedGroupHeader) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({1, 2});
StopAnimating(tab_strip);
EXPECT_EQ(4, model->count());
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(1, 3));
EXPECT_FALSE(model->IsGroupCollapsed(group));
tab_strip->controller()->ToggleTabGroupCollapsedState(group);
StopAnimating(tab_strip);
EXPECT_TRUE(model->IsGroupCollapsed(group));
// Dragging the first tab to the right should cause it to swap places with the
// collapsed group header.
ASSERT_TRUE(
PressInput(test::GetLeftCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(
test::GetRightCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_EQ("1 2 0 3", IDString(model));
EXPECT_EQ(model->group_model()->GetTabGroup(group)->ListTabs(),
gfx::Range(0, 2));
EXPECT_TRUE(model->IsGroupCollapsed(group));
}
// Drags a tab group by the header and while detached presses escape to revert
// the drag.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
RevertCollapsedHeaderDragWhileDetached) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 1);
tab_groups::TabGroupId group = model->AddToNewGroup({0});
EXPECT_FALSE(model->IsGroupCollapsed(group));
EnsureFocusToTabStrip(tab_strip);
tab_strip->controller()->ToggleTabGroupCollapsedState(group);
StopAnimating(tab_strip);
EXPECT_TRUE(model->IsGroupCollapsed(group));
DragToDetachGroupAndNotify(
tab_strip, base::BindOnce(&PressEscapeWhileDetachedHeaderStep2, this),
group);
EXPECT_FALSE(tab_strip->group_header(group)->dragging());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(1u, browser_list->size());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
std::vector<tab_groups::TabGroupId> groups =
model->group_model()->ListTabGroups();
EXPECT_EQ(1u, groups.size());
EXPECT_EQ(model->group_model()->GetTabGroup(groups[0])->ListTabs(),
gfx::Range(0, 1));
EXPECT_TRUE(tab_strip->controller()->IsGroupCollapsed(group));
}
// Creates a browser with four tabs. The first two tabs belong in Tab Group 1.
// Dragging the collapsed group header of Tab Group 1 will result in Tab Group 1
// expanding.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragCollapsedGroupHeaderExpandsGroup) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
TabGroupModel* group_model = model->group_model();
AddTabsAndResetBrowser(browser(), 3);
tab_groups::TabGroupId group = model->AddToNewGroup({2, 3});
ASSERT_FALSE(model->IsGroupCollapsed(group));
tab_strip->controller()->ToggleTabGroupCollapsedState(group);
StopAnimating(tab_strip);
ASSERT_TRUE(model->IsGroupCollapsed(group));
EnsureFocusToTabStrip(tab_strip);
ASSERT_EQ(4, model->count());
ASSERT_EQ(2u, group_model->GetTabGroup(group)->ListTabs().length());
// Drag group1, this should expand the group.
ASSERT_TRUE(
PressInput(GetCenterInScreenCoordinates(tab_strip->group_header(group))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
ASSERT_TRUE(TabDragController::IsActive());
EXPECT_FALSE(model->IsGroupCollapsed(group));
ASSERT_TRUE(ReleaseInput());
StopAnimating(tab_strip);
EXPECT_FALSE(model->IsGroupCollapsed(group));
}
// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
// complete.
#if defined(OS_MAC) || (defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
// Test is flaky on Mac and Linux: https://crbug.com/1167249
#define MAYBE_DragCollapsedGroupHeaderToSeparateWindow \
DISABLED_DragCollapsedGroupHeaderToSeparateWindow
#else
#define MAYBE_DragCollapsedGroupHeaderToSeparateWindow \
DragCollapsedGroupHeaderToSeparateWindow
#endif
// Creates two browsers, then drags a collapsed group from one to the other.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragCollapsedGroupHeaderToSeparateWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
TabStripModel* model = browser()->tab_strip_model();
AddTabsAndResetBrowser(browser(), 2);
tab_groups::TabGroupId group = model->AddToNewGroup({0, 1});
EXPECT_FALSE(model->IsGroupCollapsed(group));
tab_strip->controller()->ToggleTabGroupCollapsedState(group);
StopAnimating(tab_strip);
EXPECT_TRUE(model->IsGroupCollapsed(group));
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
TabStripModel* model2 = browser2->tab_strip_model();
StopAnimating(tab_strip2);
// Drag the group by its header into the second browser.
DragToDetachGroupAndNotify(
tab_strip,
base::BindOnce(&DragToSeparateWindowStep2, this, tab_strip, tab_strip2),
group);
ASSERT_TRUE(ReleaseInput());
// Expect the group to be in browser2, but with a new tab_groups::TabGroupId
// and not collapsed.
EXPECT_EQ("100 0 1", IDString(model2));
std::vector<tab_groups::TabGroupId> browser2_groups =
model2->group_model()->ListTabGroups();
EXPECT_EQ(1u, browser2_groups.size());
EXPECT_EQ(model2->group_model()->GetTabGroup(browser2_groups[0])->ListTabs(),
gfx::Range(1, 3));
ASSERT_FALSE(tab_strip->controller()->IsGroupCollapsed(browser2_groups[0]));
EXPECT_EQ(browser2_groups[0], group);
}
class DetachToBrowserTabDragControllerTestWithScrollableTabStripEnabled
: public DetachToBrowserTabDragControllerTest {
public:
DetachToBrowserTabDragControllerTestWithScrollableTabStripEnabled() {
scoped_feature_list_.InitWithFeatures({features::kScrollableTabStrip}, {});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// TODO(crbug.com/1052397): Revisit once build flag switch of lacros-chrome is
// complete.
// Disabling on macOS due to DCHECK crashes; see https://crbug.com/1183043.
#if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) || \
(defined(OS_MAC) && DCHECK_IS_ON())
#define MAYBE_DraggingRightExpandsTabStripSize \
DISABLED_DraggingRightExpandsTabStripSize
#else
#define MAYBE_DraggingRightExpandsTabStripSize DraggingRightExpandsTabStripSize
#endif
// Creates a browser with two tabs and drags the rightmost tab. Given the
// browser window is large enough, the tabstrip should expand to accommodate
// this tab. Note: There must be at least two tabs because dragging a singular
// tab will drag the window.
// Disabled for Linux due to test dragging flakiness.
IN_PROC_BROWSER_TEST_P(
DetachToBrowserTabDragControllerTestWithScrollableTabStripEnabled,
MAYBE_DraggingRightExpandsTabStripSize) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
browser()->window()->SetBounds(
gfx::Rect(0, 0, TabStyle::GetStandardWidth() * 4, 400));
const int tab_strip_width = tab_strip->width();
const gfx::Point tab_1_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(1));
ASSERT_TRUE(PressInput(tab_1_center));
ASSERT_TRUE(DragInputTo(tab_1_center +
gfx::Vector2d(TabStyle::GetStandardWidth(), 0)));
BrowserView::GetBrowserViewForBrowser(browser())
->GetWidget()
->LayoutRootViewIfNecessary();
EXPECT_EQ(tab_strip_width + TabStyle::GetStandardWidth(), tab_strip->width());
ASSERT_TRUE(ReleaseInput());
}
namespace {
// Invoked from the nested run loop.
void DragAllToSeparateWindowAndCancelStep2(
DetachToBrowserTabDragControllerTest* test,
TabStrip* attached_tab_strip,
TabStrip* target_tab_strip,
const BrowserList* browser_list) {
ASSERT_TRUE(attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, browser_list->size());
// Drag to target_tab_strip. This should stop the nested loop from dragging
// the window.
ASSERT_TRUE(
test->DragInputToAsync(GetCenterInScreenCoordinates(target_tab_strip)));
}
} // namespace
#if defined(OS_MAC) /* && defined(ARCH_CPU_ARM64) */
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
// These were flaking on all macs, so commented out ARCH_ above for
// crbug.com/1160917 too.
#define MAYBE_DragAllToSeparateWindowAndCancel \
DISABLED_DragAllToSeparateWindowAndCancel
#else
// TODO(https://crbug.com/1163775): Flaky on Windows and Linux.
#define MAYBE_DragAllToSeparateWindowAndCancel \
DISABLED_DragAllToSeparateWindowAndCancel
#endif
// Creates two browsers, selects all tabs in first, drags into second, then hits
// escape.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragAllToSeparateWindowAndCancel) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
browser()->tab_strip_model()->ToggleSelectionAt(0);
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragAllToSeparateWindowAndCancelStep2, this,
tab_strip, tab_strip2, browser_list));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(1u, browser_list->size());
// Cancel the drag.
ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
browser2, ui::VKEY_ESCAPE, false, false, false, false));
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("100 0 1", IDString(browser2->tab_strip_model()));
// browser() will have been destroyed, but browser2 should remain.
ASSERT_EQ(1u, browser_list->size());
EXPECT_FALSE(GetIsDragged(browser2));
// Remaining browser window should not be maximized
EXPECT_FALSE(browser2->window()->IsMaximized());
}
#if defined(OS_MAC) /* && defined(ARCH_CPU_ARM64) */
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
// These were flaking on all macs, so commented out ARCH_ above for
// crbug.com/1160917 too.
#define MAYBE_DragAllToSeparateWindowWithPinnedTabs \
DISABLED_DragAllToSeparateWindowWithPinnedTabs
#else
// TODO(https://crbug.com/1163775): Flaky on Windows and Linux.
#define MAYBE_DragAllToSeparateWindowWithPinnedTabs \
DISABLED_DragAllToSeparateWindowWithPinnedTabs
#endif
// Creates two browsers, selects all tabs in first, drags into second, then hits
// escape.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragAllToSeparateWindowWithPinnedTabs) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Open a second tab.
AddTabsAndResetBrowser(browser(), 1);
// Re-select the first one.
browser()->tab_strip_model()->ToggleSelectionAt(0);
// Create another browser.
Browser* target_browser = CreateAnotherBrowserAndResize();
TabStrip* target_tab_strip = GetTabStripForBrowser(target_browser);
// Pin the tab in the target tabstrip.
target_browser->tab_strip_model()->SetTabPinned(0, true);
// Drag the selected tabs to |target_tab_strip|.
DragTabAndNotify(
tab_strip, base::BindOnce(&DragAllToSeparateWindowStep2, this, tab_strip,
target_tab_strip));
// Should now be attached to |target_tab_strip|.
ASSERT_TRUE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(1u, browser_list->size());
// Drag to the trailing end of the tabstrip to ensure we're in a consistent
// spot within the strip.
StopAnimating(target_tab_strip);
ASSERT_TRUE(
DragInputTo(GetCenterInScreenCoordinates(target_tab_strip->tab_at(1))));
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("100 0 1", IDString(target_browser->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(target_browser));
// Remaining browser window should not be maximized
EXPECT_FALSE(target_browser->window()->IsMaximized());
}
// Creates two browsers, drags from first into the second in such a way that
// no detaching should happen.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragDirectlyToSecondWindow) {
// TODO(pkasting): Crashes when detaching browser. https://crbug.com/918733
if (input_source() == INPUT_SOURCE_TOUCH) {
VLOG(1) << "Test is DISABLED for touch input.";
return;
}
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
const gfx::Rect initial_bounds(browser2->window()->GetBounds());
// Place the first browser directly below the second in such a way that
// dragging a tab upwards will drag it directly into the second browser's
// tabstrip.
const BrowserView* const browser_view2 =
BrowserView::GetBrowserViewForBrowser(browser2);
const gfx::Rect tabstrip_region2_bounds =
browser_view2->frame()->GetBoundsForTabStripRegion(
browser_view2->tab_strip_region_view()->GetMinimumSize());
gfx::Rect bounds = initial_bounds;
bounds.Offset(0, tabstrip_region2_bounds.bottom());
browser()->window()->SetBounds(bounds);
// Ensure the first browser is on top so clicks go to it.
ui_test_utils::BrowserActivationWaiter activation_waiter(browser());
browser()->window()->Activate();
activation_waiter.WaitForActivation();
// Move to the first tab and drag it to browser2.
gfx::Point tab_0_center(GetCenterInScreenCoordinates(tab_strip->tab_at(0)));
ASSERT_TRUE(PressInput(tab_0_center));
const views::View* tab = tab_strip2->tab_at(0);
gfx::Point b2_location(GetCenterInScreenCoordinates(tab));
b2_location.Offset(-tab->width() / 4, 0);
ASSERT_TRUE(DragInputTo(b2_location));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 100", IDString(browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// Make sure that the window is still managed and not user moved.
EXPECT_TRUE(IsWindowPositionManaged(browser2->window()->GetNativeWindow()));
EXPECT_FALSE(HasUserChangedWindowPositionOrSize(
browser2->window()->GetNativeWindow()));
// Also make sure that the drag to window position has not changed.
EXPECT_EQ(initial_bounds.ToString(),
browser2->window()->GetBounds().ToString());
}
// Flaky. https://crbug.com/1176998
#if (defined(OS_MAC) && defined(ARCH_CPU_ARM64)) || defined(OS_LINUX)
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
#define MAYBE_DragSingleTabToSeparateWindow \
DISABLED_DragSingleTabToSeparateWindow
#else
#define MAYBE_DragSingleTabToSeparateWindow DragSingleTabToSeparateWindow
#endif
// Creates two browsers, the first browser has a single tab and drags into the
// second browser.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_DragSingleTabToSeparateWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
ResetIDs(browser()->tab_strip_model(), 0);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
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.
DragTabAndNotify(tab_strip, base::BindOnce(&DragAllToSeparateWindowStep2,
this, tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(1u, browser_list->size());
// Drag to the trailing end of the tabstrip to ensure we're in a consistent
// spot within the strip.
StopAnimating(tab_strip2);
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip2->tab_at(1))));
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("100 0", IDString(browser2->tab_strip_model()));
EXPECT_FALSE(GetIsDragged(browser2));
// Remaining browser window should not be maximized
EXPECT_FALSE(browser2->window()->IsMaximized());
}
#if !defined(OS_MAC)
namespace {
// Invoked from the nested run loop.
void CancelOnNewTabWhenDraggingStep2(
DetachToBrowserTabDragControllerTest* test,
const BrowserList* browser_list) {
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, browser_list->size());
chrome::AddTabAt(browser_list->GetLastActive(), GURL(url::kAboutBlankURL),
0, false);
}
} // namespace
// Adds another tab, detaches into separate window, adds another tab and
// verifies the run loop ends.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
CancelOnNewTabWhenDragging) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Move to the first tab and drag it enough so that it detaches.
const gfx::Point tab_0_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(0));
ASSERT_TRUE(PressInput(tab_0_center));
// Add another tab. This should trigger exiting the nested loop. Add at the
// beginning to exercise past crash when model/tabstrip got out of sync.
// crbug.com/474082
content::WindowedNotificationObserver observer(
content::NOTIFICATION_LOAD_STOP,
content::NotificationService::AllSources());
ASSERT_TRUE(DragInputToNotifyWhenDone(
tab_0_center + gfx::Vector2d(0, GetDetachY(tab_strip)),
base::BindOnce(&CancelOnNewTabWhenDraggingStep2, this, browser_list)));
observer.Wait();
// Should be two windows and not dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
ASSERT_EQ(2u, browser_list->size());
for (auto* browser : *BrowserList::GetInstance()) {
EXPECT_FALSE(GetIsDragged(browser));
// Should not be maximized
EXPECT_FALSE(browser->window()->IsMaximized());
}
}
#endif
namespace {
TabStrip* GetAttachedTabstrip() {
for (Browser* browser : *BrowserList::GetInstance()) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
if (TabDragController::IsAttachedTo(
browser_view->tabstrip()->GetDragContext()))
return browser_view->tabstrip();
}
return nullptr;
}
void DragWindowAndVerifyOffset(DetachToBrowserTabDragControllerTest* test,
TabStrip* tab_strip,
int tab_index) {
test::QuitDraggingObserver observer(tab_strip);
// Move to the tab and drag it enough so that it detaches.
const gfx::Point tab_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(tab_index));
// The expected offset; the horizontal position should be relative to the
// pressed tab. The vertical position should be relative to the window itself
// since the top margin can be different between the existing browser and
// the dragged one.
const gfx::Vector2d press_offset(
tab_center.x() - tab_strip->tab_at(tab_index)->GetBoundsInScreen().x(),
tab_center.y() - tab_strip->GetWidget()->GetWindowBoundsInScreen().y());
const gfx::Point initial_move =
tab_center + gfx::Vector2d(0, GetDetachY(tab_strip));
const gfx::Point second_move = initial_move + gfx::Vector2d(20, 20);
ASSERT_TRUE(test->PressInput(tab_center));
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
initial_move, base::BindLambdaForTesting([&]() {
// Moves slightly to cause the actual dragging effect on the system and
// makes sure the window is positioned correctly.
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
second_move, base::BindLambdaForTesting([&]() {
TabStrip* attached = GetAttachedTabstrip();
// Same computation for drag offset. This operation drags a single
// tab, so the target tab index should be always 0.
gfx::Vector2d drag_offset(
second_move.x() -
attached->tab_at(0)->GetBoundsInScreen().x(),
second_move.y() -
attached->GetWidget()->GetWindowBoundsInScreen().y());
EXPECT_EQ(press_offset, drag_offset);
ASSERT_TRUE(test->ReleaseInput());
})));
})));
observer.Wait();
}
} // namespace
#if defined(OS_WIN)
// TODO(mukai): enable those tests on Windows.
#define MAYBE_OffsetForDraggingTab DISABLED_OffsetForDraggingTab
#else
#define MAYBE_OffsetForDraggingTab OffsetForDraggingTab
#endif
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
MAYBE_OffsetForDraggingTab) {
DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 0);
ASSERT_FALSE(TabDragController::IsActive());
}
// TODO(960915): fix flakiness and re-enable this test on mac/linux.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DISABLED_OffsetForDraggingDetachedTab) {
AddTabsAndResetBrowser(browser(), 1);
DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 1);
ASSERT_FALSE(TabDragController::IsActive());
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
namespace {
void DragInMaximizedWindowStep2(DetachToBrowserTabDragControllerTest* test,
Browser* browser,
TabStrip* tab_strip,
const BrowserList* browser_list) {
// There should be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
EXPECT_NE(browser, new_browser);
ASSERT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
// Both windows should be visible.
EXPECT_TRUE(tab_strip->GetWidget()->IsVisible());
EXPECT_TRUE(tab_strip2->GetWidget()->IsVisible());
// Stops dragging.
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Creates a browser with two tabs, maximizes it, drags the tab out.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragInMaximizedWindow) {
AddTabsAndResetBrowser(browser(), 1);
browser()->window()->Maximize();
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragInMaximizedWindowStep2, this, browser(),
tab_strip, browser_list));
ASSERT_FALSE(TabDragController::IsActive());
// Should be two browsers.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(new_browser->window()->IsActive());
EXPECT_TRUE(browser()->window()->GetNativeWindow()->IsVisible());
EXPECT_TRUE(new_browser->window()->GetNativeWindow()->IsVisible());
EXPECT_FALSE(GetIsDragged(browser()));
EXPECT_FALSE(GetIsDragged(new_browser));
// The source window should be maximized.
EXPECT_TRUE(browser()->window()->IsMaximized());
// The new window should be maximized.
EXPECT_TRUE(new_browser->window()->IsMaximized());
}
namespace {
void NewBrowerWindowStateStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* tab_strip) {
// There should be two browser windows, including the newly created one for
// the dragged tab.
EXPECT_EQ(3u, test->browser_list->size());
// Get this new created window for the dragged tab.
Browser* new_browser = test->browser_list->get(2);
aura::Window* window = new_browser->window()->GetNativeWindow();
EXPECT_NE(window->GetProperty(aura::client::kShowStateKey),
ui::SHOW_STATE_MAXIMIZED);
EXPECT_EQ(window->GetProperty(aura::client::kShowStateKey),
ui::SHOW_STATE_DEFAULT);
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Test that tab dragging can work on a browser window with its initial show
// state is MAXIMIZED.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
NewBrowerWindowState) {
// Create a browser window whose initial show state is MAXIMIZED.
Browser::CreateParams params(browser()->profile(), /*user_gesture=*/false);
params.initial_show_state = ui::SHOW_STATE_MAXIMIZED;
Browser* browser = Browser::Create(params);
AddBlankTabAndShow(browser);
TabStrip* tab_strip = GetTabStripForBrowser(browser);
AddTabsAndResetBrowser(browser, 1);
// Maximize the browser window.
browser->window()->Maximize();
EXPECT_EQ(browser->window()->GetNativeWindow()->GetProperty(
aura::client::kShowStateKey),
ui::SHOW_STATE_MAXIMIZED);
// Drag it far enough that the first tab detaches.
DragTabAndNotify(tab_strip,
base::BindOnce(&NewBrowerWindowStateStep2, this, tab_strip));
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
OffsetForDraggingInMaximizedWindow) {
AddTabsAndResetBrowser(browser(), 1);
// Moves the browser window slightly to ensure that the browser's restored
// bounds are different from the maximized bound's origin.
browser()->window()->SetBounds(browser()->window()->GetBounds() +
gfx::Vector2d(100, 50));
browser()->window()->Maximize();
DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 0);
ASSERT_FALSE(TabDragController::IsActive());
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
OffsetForDraggingInTabletMode) {
AddTabsAndResetBrowser(browser(), 1);
// Moves the browser window slightly to ensure that the browser's restored
// bounds are different from the maximized bound's origin.
browser()->window()->SetBounds(browser()->window()->GetBounds() +
gfx::Vector2d(100, 50));
BrowserView::GetBrowserViewForBrowser(browser())
->DisableTopControlsSlideForTesting();
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 1);
ASSERT_FALSE(TabDragController::IsActive());
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
OffsetForDraggingRightSnappedWindowInTabletMode) {
BrowserView::GetBrowserViewForBrowser(browser())
->DisableTopControlsSlideForTesting();
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
// Right snap the browser window.
aura::Window* window = browser()->window()->GetNativeWindow();
ash::SplitViewTestApi().SnapWindow(
window, ash::SplitViewTestApi::SnapPosition::RIGHT);
EXPECT_NE(gfx::Point(), window->GetBoundsInScreen().origin());
DragWindowAndVerifyOffset(this, GetTabStripForBrowser(browser()), 0);
ASSERT_FALSE(TabDragController::IsActive());
}
namespace {
void DragToOverviewWindowStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_FALSE(not_attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// And there should be three browser windows, including the newly created one
// for the dragged tab.
EXPECT_EQ(3u, test->browser_list->size());
// Put the window that accociated with |target_tab_strip| in overview.
test::GetWindowForTabStrip(target_tab_strip)
->SetProperty(chromeos::kIsShowingInOverviewKey, true);
// Drag to target_tab_strip.
ASSERT_TRUE(
test->DragInputTo(GetCenterInScreenCoordinates(target_tab_strip)));
// Test that the dragged tab did not attach to the overview window.
EXPECT_EQ(3u, test->browser_list->size());
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Creates two browsers, drags from first into second. If the target window is
// currently showing in overview, we should not attaching the dragged tabs
// into the target window during dragging, but should do so until the drag ends.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragToOverviewWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Add another tab to browser().
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
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.
DragTabAndNotify(tab_strip, base::BindOnce(&DragToOverviewWindowStep2, this,
tab_strip, tab_strip2));
// Now the dragged tab should have been attached to the target tabstrip after
// the drag ends.
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(2u, browser_list->size());
}
namespace {
void DragToOverviewNewWindowItemStep2(
DetachToBrowserTabDragControllerTest* test,
TabStrip* attached_tab_strip) {
ASSERT_TRUE(attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
EXPECT_TRUE(attached_tab_strip->GetWidget()->GetNativeWindow()->HasFocus());
// Put the attached window in overview to simulate the "drop on the new
// selector item" scenario.
test::GetWindowForTabStrip(attached_tab_strip)
->SetProperty(chromeos::kIsShowingInOverviewKey, true);
// At the same time we remove |attached_tab_strip|'s focus to simulate what
// happens in overview (In overview, the window items in overview don't have
// focus, it's the textfield in overview that has focus).
attached_tab_strip->GetFocusManager()->SetFocusedView(nullptr);
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// After dragging a window to drop it onto the new window selector item in
// overview mode, the window should be added to overview window grid, and should
// not restore its focus.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DragToOverviewNewWindowItem) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Make |tab_strip| the focused view before dragging.
tab_strip->GetFocusManager()->SetFocusedView(tab_strip);
EXPECT_TRUE(tab_strip->HasFocus());
// Drag the tab long enough so that it moves.
DragTabAndNotify(tab_strip, base::BindOnce(&DragToOverviewNewWindowItemStep2,
this, tab_strip));
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(1u, browser_list->size());
// Test that the attached tabstrip doesn't restore focuas as it's currently
// showing in overview.
EXPECT_TRUE(test::GetWindowForTabStrip(tab_strip)->GetProperty(
chromeos::kIsShowingInOverviewKey));
EXPECT_FALSE(tab_strip->HasFocus());
}
namespace {
// A window observer that observes the dragged window's property
// ash::kIsDraggingTabsKey.
class DraggedWindowObserver : public aura::WindowObserver {
public:
DraggedWindowObserver(DetachToBrowserTabDragControllerTest* test,
aura::Window* window,
const gfx::Rect& bounds,
const gfx::Point& end_point)
: test_(test), end_bounds_(bounds), end_point_(end_point) {}
DraggedWindowObserver(const DraggedWindowObserver&) = delete;
DraggedWindowObserver& operator=(const DraggedWindowObserver&) = delete;
~DraggedWindowObserver() override {
if (window_)
window_->RemoveObserver(this);
}
void StartObserving(aura::Window* window) {
DCHECK(!window_);
window_ = window;
window_->AddObserver(this);
}
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override {
DCHECK_EQ(window_, window);
window_->RemoveObserver(this);
window_ = nullptr;
}
void OnWindowPropertyChanged(aura::Window* window,
const void* key,
intptr_t old) override {
DCHECK_EQ(window_, window);
if (key == ash::kIsDraggingTabsKey) {
if (!window_->GetProperty(ash::kIsDraggingTabsKey)) {
// It should be triggered by TabDragController::ClearTabDraggingInfo()
// from TabDragController::EndDragImpl(). Theoretically at this point
// TabDragController should have removed itself as an observer of the
// dragged tabstrip's widget. So changing its bounds should do nothing.
// It's to ensure the current cursor location is within the bounds of
// another browser's tabstrip.
test_->MoveInputTo(end_point_);
// Change window's bounds to simulate what might happen in ash. If
// TabDragController is still an observer of the dragged tabstrip's
// widget, OnWidgetBoundsChanged() will calls into ContinueDragging()
// to attach the dragged tabstrip into another browser, which might
// cause chrome crash.
window_->SetBounds(end_bounds_);
}
}
}
private:
DetachToBrowserTabDragControllerTest* test_;
// The dragged window.
aura::Window* window_ = nullptr;
// The bounds that |window_| will change to when the drag ends.
gfx::Rect end_bounds_;
// The position that the mouse/touch event will move to when the drag ends.
gfx::Point end_point_;
};
void DoNotObserveDraggedWidgetAfterDragEndsStep2(
DetachToBrowserTabDragControllerTest* test,
DraggedWindowObserver* observer,
TabStrip* attached_tab_strip) {
ASSERT_TRUE(attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Start observe the dragged window.
observer->StartObserving(attached_tab_strip->GetWidget()->GetNativeWindow());
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Test that after the drag ends, TabDragController is no longer an observer of
// the dragged widget, so that if the bounds of the dragged widget change,
// TabDragController won't be put into dragging process again.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DoNotObserveDraggedWidgetAfterDragEnds) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
EXPECT_EQ(2u, browser_list->size());
// Create an window observer to observe the dragged window.
std::unique_ptr<DraggedWindowObserver> observer(new DraggedWindowObserver(
this, test::GetWindowForTabStrip(tab_strip),
tab_strip2->GetWidget()->GetNativeWindow()->bounds(),
GetCenterInScreenCoordinates(tab_strip2)));
// Drag the tab long enough so that it moves.
DragTabAndNotify(tab_strip,
base::BindOnce(&DoNotObserveDraggedWidgetAfterDragEndsStep2,
this, observer.get(), tab_strip));
// There should be still two browsers at this moment. |tab_strip| should not
// be merged into |tab_strip2|.
EXPECT_EQ(2u, browser_list->size());
ASSERT_FALSE(TabDragController::IsActive());
}
namespace {
void DoNotAttachToOtherWindowTestStep2(
DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_TRUE(TabDragController::IsActive());
// There should be three browser windows, including the newly created one for
// the dragged tab.
EXPECT_EQ(3u, test->browser_list->size());
// Get this new created window and set it to non-attachable.
Browser* new_browser = test->browser_list->get(2);
new_browser->window()->GetNativeWindow()->SetProperty(
ash::kCanAttachToAnotherWindowKey, false);
// Now drag to target_tab_strip.
ASSERT_TRUE(
test->DragInputTo(GetCenterInScreenCoordinates(target_tab_strip)));
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Test that if the dragged window is not allowed to attach to another window
// during dragging, then it can't attach to another window.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DoNotAttachToOtherWindowTest) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
EXPECT_EQ(2u, browser_list->size());
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip, base::BindOnce(&DoNotAttachToOtherWindowTestStep2,
this, tab_strip, tab_strip2));
// Test that the newly created browser window doesn't attach to the target
// browser window.
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(3u, browser_list->size());
}
namespace {
void DeferredTargetTabStripTestStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_FALSE(not_attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// And there should be three browser windows, including the newly created one
// for the dragged tab.
EXPECT_EQ(3u, test->browser_list->size());
// Put the window that accociated with |target_tab_strip| in overview.
test::GetWindowForTabStrip(target_tab_strip)
->SetProperty(chromeos::kIsShowingInOverviewKey, true);
// Drag to target_tab_strip.
ASSERT_TRUE(
test->DragInputTo(GetCenterInScreenCoordinates(target_tab_strip)));
// At this point, |target_tab_strip| should be the deferred target tabstip.
// Theoratically the dragged tabstrip will merge into |target_tab_strip| after
// the drag ends.
EXPECT_TRUE(test::GetWindowForTabStrip(target_tab_strip)
->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey));
// Now clear the property.
test::GetWindowForTabStrip(target_tab_strip)
->ClearProperty(ash::kIsDeferredTabDraggingTargetWindowKey);
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Test that if a tabstrip is a deferred target tabstrip, and its corresponding
// window key is cleared to remove itself as the deferred target tabstrip, the
// dragged tabstrip should not attach into it after the drag ends.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DeferredTargetTabStripTest) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
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.
DragTabAndNotify(tab_strip, base::BindOnce(&DeferredTargetTabStripTestStep2,
this, tab_strip, tab_strip2));
// Now the dragged tab should not be attached to the target tabstrip after
// the drag ends.
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(3u, browser_list->size());
}
namespace {
// Returns true if the web contents that's accociated with |browser| is using
// fast resize.
bool WebContentsIsFastResized(Browser* browser) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser);
ContentsWebView* contents_web_view =
static_cast<ContentsWebView*>(browser_view->GetContentsView());
return contents_web_view->holder()->fast_resize();
}
void FastResizeDuringDraggingStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
// There should be three browser windows, including the newly created one for
// the dragged tab.
EXPECT_EQ(3u, test->browser_list->size());
// TODO(crbug.com/1110266): Remove explicit OS_CHROMEOS check once OS_LINUX
// CrOS cleanup is done.
// TODO(crbug.com/1052397): Revisit the macro expression once build flag switch
// of lacros-chrome is complete.
#if !(defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
// Get this new created window for the drag. It should have fast resize set.
Browser* new_browser = test->browser_list->get(2);
EXPECT_TRUE(WebContentsIsFastResized(new_browser));
// The source window should also have fast resize set.
EXPECT_TRUE(WebContentsIsFastResized(test->browser()));
#endif
// Now drag to target_tab_strip.
ASSERT_TRUE(
test->DragInputTo(GetCenterInScreenCoordinates(target_tab_strip)));
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Tests that we use fast resize to resize the web contents of the dragged
// window and the source window during tab dragging process, and don't use fast
// resize after tab dragging ends.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
FastResizeDuringDragging) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
EXPECT_EQ(2u, browser_list->size());
EXPECT_FALSE(WebContentsIsFastResized(browser()));
EXPECT_FALSE(WebContentsIsFastResized(browser2));
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip, base::BindOnce(&FastResizeDuringDraggingStep2,
this, tab_strip, tab_strip2));
EXPECT_FALSE(WebContentsIsFastResized(browser()));
EXPECT_FALSE(WebContentsIsFastResized(browser2));
}
namespace {
void DragToMinimizedOverviewWindowStep2(
DetachToBrowserTabDragControllerTest* test,
TabStrip* dragged_tab_strip,
TabStrip* target_tab_strip) {
EXPECT_EQ(2u, test->browser_list->size());
// Test that overview is open behind the dragged window and |target_window|
// has been put in overview as expected.
aura::Window* dragged_window = test::GetWindowForTabStrip(dragged_tab_strip);
aura::Window* target_window = test::GetWindowForTabStrip(target_tab_strip);
EXPECT_TRUE(target_window->GetProperty(chromeos::kIsShowingInOverviewKey));
EXPECT_FALSE(dragged_window->GetProperty(chromeos::kIsShowingInOverviewKey));
// Now drag the tabs to a point that is contained by |target_window|.
gfx::RectF target_window_bounds(target_window->bounds());
gfx::Transform transform = target_window->layer()->GetTargetTransform();
transform.TransformRect(&target_window_bounds);
gfx::Point target_point(target_window_bounds.CenterPoint().x(),
target_window_bounds.CenterPoint().y());
// Minimize the |target_window|.
target_window->SetProperty(aura::client::kShowStateKey,
ui::SHOW_STATE_MINIMIZED);
ASSERT_TRUE(test->DragInputTo(target_point));
EXPECT_TRUE(
target_window->GetProperty(ash::kIsDeferredTabDraggingTargetWindowKey));
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// Test that the dragged tabs should be able to merge into an overview window
// that represents a minimized window.
// TODO(https://crbug.com/979013) Disabled due to flakiness.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DISABLED_DragToMinimizedOverviewWindow) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
EXPECT_EQ(2u, browser_list->size());
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
// Move to the first tab of |browser2| and drag it toward to browser(). Note
// dragging on |browser2| which only has one tab in tablet mode will open
// overview behind the |browser2|.
DragTabAndNotify(tab_strip2,
base::BindOnce(&DragToMinimizedOverviewWindowStep2, this,
tab_strip2, tab_strip));
// |tab_strip| should have been merged into |browser2|. Thus there should only
// be one browser now.
EXPECT_EQ(browser_list->size(), 1u);
}
namespace {
void WindowSizeDuringDraggingTestStep2(
DetachToBrowserTabDragControllerTest* test,
TabStrip* tab_strip) {
// There should be two browser windows, including the newly created one for
// the dragged tab.
EXPECT_EQ(2u, test->browser_list->size());
// Get this new created window for the dragged tab.
Browser* new_browser = test->browser_list->get(1);
aura::Window* window = new_browser->window()->GetNativeWindow();
const gfx::Size work_area = display::Screen::GetScreen()
->GetDisplayNearestWindow(window)
.work_area()
.size();
const gfx::Size minimum_size = window->delegate()->GetMinimumSize();
const gfx::Size window_size = window->bounds().size();
EXPECT_GE(minimum_size.width(),
BrowserViewLayout::kMainBrowserContentsMinimumWidth);
EXPECT_GE(window_size.width(), minimum_size.width());
EXPECT_GE(window_size.height(), minimum_size.height());
EXPECT_EQ(window_size,
gfx::Size(std::max(work_area.width() / 2, minimum_size.width()),
std::max(work_area.height() / 2, minimum_size.height())));
ASSERT_TRUE(test->ReleaseInput());
}
} // namespace
// TODO(http://crbug.com/1028386): Test fails flakily.
// Tests that when drgging a tab out of a browser window, the dragged window's
// size should be equal or larger than its minimum size.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
DISABLED_WindowSizeDuringDraggingTest) {
// Set the display size small enough.
display::test::DisplayManagerTestApi(ash::Shell::Get()->display_manager())
.UpdateDisplay(base::NumberToString(
BrowserViewLayout::kMainBrowserContentsMinimumWidth) +
"x400");
ash::ShellTestApi().SetTabletModeEnabledForTest(true);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Drag it far enough that the first tab detaches.
DragTabAndNotify(tab_strip, base::BindOnce(&WindowSizeDuringDraggingTestStep2,
this, tab_strip));
}
class DetachToBrowserTabDragControllerTestWithTabbedSystemApp
: public DetachToBrowserTabDragControllerTest {
public:
DetachToBrowserTabDragControllerTestWithTabbedSystemApp()
: test_system_web_app_installation_(
web_app::TestSystemWebAppInstallation::
SetUpTabbedMultiWindowApp()) {}
web_app::AppId InstallMockApp() {
test_system_web_app_installation_->WaitForAppInstall();
return test_system_web_app_installation_->GetAppId();
}
Browser* LaunchWebAppBrowser(web_app::AppId app_id) {
return web_app::LaunchWebAppBrowser(browser()->profile(), app_id);
}
const GURL& GetAppUrl() {
return test_system_web_app_installation_->GetAppUrl();
}
private:
std::unique_ptr<web_app::TestSystemWebAppInstallation>
test_system_web_app_installation_;
};
// Move tab from TYPE_APP Browser to create new TYPE_APP Browser.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabbedSystemApp,
DragAppToOwnWindow) {
// Install and get a tabbed system app.
web_app::AppId tabbed_app_id = InstallMockApp();
Browser* app_browser = LaunchWebAppBrowser(tabbed_app_id);
ASSERT_EQ(2u, browser_list->size());
// Close normal browser since other code expects only 1 browser to start.
CloseBrowserSynchronously(browser());
ASSERT_EQ(1u, browser_list->size());
SelectFirstBrowser();
ASSERT_EQ(app_browser, browser());
EXPECT_EQ(Browser::Type::TYPE_APP, browser_list->get(0)->type());
AddTabsAndResetBrowser(browser(), 1, GetAppUrl());
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
int tab_0_width = tab_strip->tab_at(0)->width();
DragTabAndNotify(tab_strip,
base::BindOnce(&DetachToBrowserTabDragControllerTest::
ReleaseInputAfterWindowDetached,
base::Unretained(this), tab_0_width));
// New browser should be TYPE_APP.
ASSERT_EQ(2u, browser_list->size());
EXPECT_EQ(Browser::Type::TYPE_APP, browser_list->get(1)->type());
}
// Move tab from TYPE_APP Browser to another TYPE_APP Browser.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestWithTabbedSystemApp,
DragAppToAppWindow) {
// Install and get 2 browsers with tabbed system app.
web_app::AppId tabbed_app_id = InstallMockApp();
Browser* app_browser1 = LaunchWebAppBrowser(tabbed_app_id);
Browser* app_browser2 = LaunchWebAppBrowser(tabbed_app_id);
ASSERT_EQ(3u, browser_list->size());
ResetIDs(app_browser2->tab_strip_model(), 100);
Resize(app_browser1, app_browser2);
// Close normal browser since other code expects only 1 browser to start.
CloseBrowserSynchronously(browser());
ASSERT_EQ(2u, browser_list->size());
SelectFirstBrowser();
ASSERT_EQ(app_browser1, browser());
AddTabsAndResetBrowser(browser(), 1, GetAppUrl());
TabStrip* tab_strip1 = GetTabStripForBrowser(app_browser1);
TabStrip* tab_strip2 = GetTabStripForBrowser(app_browser2);
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip1, base::BindOnce(&DragToSeparateWindowStep2, this,
tab_strip1, tab_strip2));
// Should now be attached to tab_strip2.
// Release mouse or touch, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
EXPECT_EQ("100 0", IDString(app_browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(app_browser1->tab_strip_model()));
}
// Subclass of DetachToBrowserTabDragControllerTest that
// creates multiple displays.
class DetachToBrowserInSeparateDisplayTabDragControllerTest
: public DetachToBrowserTabDragControllerTest {
public:
DetachToBrowserInSeparateDisplayTabDragControllerTest() {}
DetachToBrowserInSeparateDisplayTabDragControllerTest(
const DetachToBrowserInSeparateDisplayTabDragControllerTest&) = delete;
DetachToBrowserInSeparateDisplayTabDragControllerTest& operator=(
const DetachToBrowserInSeparateDisplayTabDragControllerTest&) = delete;
virtual ~DetachToBrowserInSeparateDisplayTabDragControllerTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
DetachToBrowserTabDragControllerTest::SetUpCommandLine(command_line);
// Make screens sufficiently wide to host 2 browsers side by side.
command_line->AppendSwitchASCII("ash-host-window-bounds",
"0+0-800x600,800+0-800x600");
}
};
namespace {
void DragSingleTabToSeparateWindowInSecondDisplayStep3(
DetachToBrowserTabDragControllerTest* test) {
ASSERT_TRUE(test->ReleaseInput());
}
void DragSingleTabToSeparateWindowInSecondDisplayStep2(
DetachToBrowserTabDragControllerTest* test,
const gfx::Point& target_point) {
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
target_point,
base::BindOnce(&DragSingleTabToSeparateWindowInSecondDisplayStep3,
test)));
}
} // namespace
// Drags from browser to a second display and releases input.
IN_PROC_BROWSER_TEST_P(DetachToBrowserInSeparateDisplayTabDragControllerTest,
DragSingleTabToSeparateWindowInSecondDisplay) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move to the first tab and drag it enough so that it detaches.
// Then drag it to the final destination on the second screen.
display::Screen* const screen = display::Screen::GetScreen();
display::Display second_display = ui_test_utils::GetSecondaryDisplay(screen);
const gfx::Point start = GetCenterInScreenCoordinates(tab_strip->tab_at(0));
ASSERT_FALSE(second_display.bounds().Contains(start));
const gfx::Point target(second_display.bounds().x() + 1,
start.y() + GetDetachY(tab_strip));
ASSERT_TRUE(second_display.bounds().Contains(target));
// TODO(crbug.com/990589): Unit tests should be able to simulate mouse input
// without having to call |CursorManager::SetDisplay|.
ash::Shell::Get()->cursor_manager()->SetDisplay(second_display);
DragTabAndNotify(
tab_strip,
base::BindOnce(&DragSingleTabToSeparateWindowInSecondDisplayStep2, this,
target));
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should now be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
// This other browser should be on the second screen (with mouse drag)
// With the touch input the browser cannot be dragged from one screen
// to another and the window stays on the first screen.
if (input_source() == INPUT_SOURCE_MOUSE) {
EXPECT_EQ(
ui_test_utils::GetSecondaryDisplay(screen).id(),
screen
->GetDisplayNearestWindow(new_browser->window()->GetNativeWindow())
.id());
}
EXPECT_EQ("0", IDString(new_browser->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// Both windows should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_FALSE(new_browser->window()->IsMaximized());
}
namespace {
// Invoked from the nested run loop.
void DragTabToWindowInSeparateDisplayStep2(
DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_FALSE(not_attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(target_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Drag to target_tab_strip. This should stop the nested loop from dragging
// the window.
gfx::Point target_point(
GetCenterInScreenCoordinates(target_tab_strip->tab_at(0)));
// Move it closer to the beginning of the tab so it will drop before that tab.
target_point.Offset(-20, 0);
ASSERT_TRUE(test->DragInputToAsync(target_point));
}
} // namespace
// Drags from browser to another browser on a second display and releases input.
IN_PROC_BROWSER_TEST_P(DetachToBrowserInSeparateDisplayTabDragControllerTest,
DragTabToWindowInSeparateDisplay) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Create another browser.
Browser* browser2 = CreateBrowser(browser()->profile());
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
ResetIDs(browser2->tab_strip_model(), 100);
// Move the second browser to the second display.
display::Screen* screen = display::Screen::GetScreen();
Display second_display = ui_test_utils::GetSecondaryDisplay(screen);
browser2->window()->SetBounds(second_display.work_area());
EXPECT_EQ(
second_display.id(),
screen->GetDisplayNearestWindow(browser2->window()->GetNativeWindow())
.id());
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragTabToWindowInSeparateDisplayStep2, this,
tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 100", IDString(browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// Both windows should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_FALSE(browser2->window()->IsMaximized());
}
// Crashes on ChromeOS. crbug.com/1003288
#if BUILDFLAG(IS_CHROMEOS_ASH)
#define MAYBE_DragBrowserWindowWhenMajorityOfBoundsInSecondDisplay \
DISABLED_CDragBrowserWindowWhenMajorityOfBoundsInSecondDisplay
#else
#define MAYBE_DragBrowserWindowWhenMajorityOfBoundsInSecondDisplay \
DragBrowserWindowWhenMajorityOfBoundsInSecondDisplay
#endif
IN_PROC_BROWSER_TEST_P(
DetachToBrowserInSeparateDisplayTabDragControllerTest,
MAYBE_DragBrowserWindowWhenMajorityOfBoundsInSecondDisplay) {
// Set the browser's window bounds such that the majority of its bounds
// resides in the second display.
const std::pair<Display, Display> displays =
GetDisplays(display::Screen::GetScreen());
TabStrip* tab_strip = GetTabStripForBrowser(browser());
{
// Moves the browser window through dragging so that the majority of its
// bounds are in the secondary display but it's still be in the primary
// display. Do not use SetBounds() or related, it may move the browser
// window to the secondary display in some configurations like Mash.
int target_x = displays.first.bounds().right() -
browser()->window()->GetBounds().width() / 2 + 20;
const gfx::Point target_point =
GetCenterInScreenCoordinates(tab_strip->tab_at(0)) +
gfx::Vector2d(target_x - browser()->window()->GetBounds().x(),
GetDetachY(tab_strip));
DragTabAndNotify(
tab_strip,
base::BindOnce(&DragSingleTabToSeparateWindowInSecondDisplayStep2, this,
target_point));
StopAnimating(tab_strip);
}
EXPECT_EQ(displays.first.id(),
browser()->window()->GetNativeWindow()->GetHost()->GetDisplayId());
// Start dragging the window by the tab strip, and move it only to the edge
// of the first display. Expect at that point mouse would warp and the window
// will therefore reside in the second display when mouse is released.
const gfx::Point tab_0_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(0));
const int offset_x = tab_0_center.x() - browser()->window()->GetBounds().x();
const int detach_y = tab_0_center.y() + GetDetachY(tab_strip);
const int first_display_warp_edge_x = displays.first.bounds().right() - 1;
const gfx::Point warped_point(displays.second.bounds().x() + 1, detach_y);
DragTabAndNotify(
tab_strip, base::BindLambdaForTesting([&]() {
// This makes another event on the warped location because the test
// system does not create it automatically as the result of pointer
// warp.
ASSERT_TRUE(DragInputToNotifyWhenDone(
gfx::Point(first_display_warp_edge_x, detach_y),
base::BindOnce(&DragSingleTabToSeparateWindowInSecondDisplayStep2,
this, warped_point)));
}));
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should only be a single browser.
ASSERT_EQ(1u, browser_list->size());
ASSERT_EQ(browser(), browser_list->get(0));
ASSERT_TRUE(browser()->window()->IsActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
// Browser now resides in display 2.
EXPECT_EQ(warped_point.x() - offset_x, browser()->window()->GetBounds().x());
EXPECT_EQ(displays.second.id(),
browser()->window()->GetNativeWindow()->GetHost()->GetDisplayId());
}
// Drags from browser to another browser on a second display and releases input.
IN_PROC_BROWSER_TEST_P(DetachToBrowserInSeparateDisplayTabDragControllerTest,
DragTabToWindowOnSecondDisplay) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Create another browser.
Browser* browser2 = CreateBrowser(browser()->profile());
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
ResetIDs(browser2->tab_strip_model(), 100);
// Move both browsers to be side by side on the second display.
display::Screen* screen = display::Screen::GetScreen();
Display second_display = ui_test_utils::GetSecondaryDisplay(screen);
gfx::Rect work_area = second_display.work_area();
work_area.set_width(work_area.width() / 2);
browser()->window()->SetBounds(work_area);
// It's possible the window will not fit in half the screen, in which case we
// will position the windows as well as we can.
work_area.set_x(browser()->window()->GetBounds().right());
// Sanity check: second browser should still be on the second display.
ASSERT_LT(work_area.x(), second_display.work_area().right());
browser2->window()->SetBounds(work_area);
EXPECT_EQ(
second_display.id(),
screen->GetDisplayNearestWindow(browser()->window()->GetNativeWindow())
.id());
EXPECT_EQ(
second_display.id(),
screen->GetDisplayNearestWindow(browser2->window()->GetNativeWindow())
.id());
// Sanity check: make sure the target position is also within in the screen
// bounds:
ASSERT_LT(GetCenterInScreenCoordinates(tab_strip2->tab_at(0)).x(),
second_display.work_area().right());
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragTabToWindowInSeparateDisplayStep2, this,
tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 100", IDString(browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// Both windows should not be maximized
EXPECT_FALSE(browser()->window()->IsMaximized());
EXPECT_FALSE(browser2->window()->IsMaximized());
}
// Drags from a maximized browser to another non-maximized browser on a second
// display and releases input.
IN_PROC_BROWSER_TEST_P(DetachToBrowserInSeparateDisplayTabDragControllerTest,
DragMaxTabToNonMaxWindowInSeparateDisplay) {
AddTabsAndResetBrowser(browser(), 1);
browser()->window()->Maximize();
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Create another browser on the second display.
display::Screen* screen = display::Screen::GetScreen();
ASSERT_EQ(2, screen->GetNumDisplays());
const std::pair<Display, Display> displays = GetDisplays(screen);
gfx::Rect work_area = displays.second.work_area();
work_area.Inset(20, 20, 20, 60);
Browser::CreateParams params(browser()->profile(), true);
params.initial_show_state = ui::SHOW_STATE_NORMAL;
params.initial_bounds = work_area;
Browser* browser2 = Browser::Create(params);
AddBlankTabAndShow(browser2);
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
ResetIDs(browser2->tab_strip_model(), 100);
EXPECT_EQ(
displays.second.id(),
screen->GetDisplayNearestWindow(browser2->window()->GetNativeWindow())
.id());
EXPECT_EQ(
displays.first.id(),
screen->GetDisplayNearestWindow(browser()->window()->GetNativeWindow())
.id());
EXPECT_EQ(2, tab_strip->GetTabCount());
EXPECT_EQ(1, tab_strip2->GetTabCount());
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragTabToWindowInSeparateDisplayStep2, this,
tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
// tab should have moved
EXPECT_EQ(1, tab_strip->GetTabCount());
EXPECT_EQ(2, tab_strip2->GetTabCount());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 100", IDString(browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// Source browser should still be maximized, target should not
EXPECT_TRUE(browser()->window()->IsMaximized());
EXPECT_FALSE(browser2->window()->IsMaximized());
}
// Drags from a restored browser to an immersive fullscreen browser on a
// second display and releases input.
// TODO(pkasting) https://crbug.com/910782 Hangs.
IN_PROC_BROWSER_TEST_P(DetachToBrowserInSeparateDisplayTabDragControllerTest,
DISABLED_DragTabToImmersiveBrowserOnSeparateDisplay) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Create another browser.
Browser* browser2 = CreateBrowser(browser()->profile());
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
ResetIDs(browser2->tab_strip_model(), 100);
// Move the second browser to the second display.
display::Screen* screen = display::Screen::GetScreen();
const std::pair<Display, Display> displays = GetDisplays(screen);
browser2->window()->SetBounds(displays.second.work_area());
EXPECT_EQ(
displays.second.id(),
screen->GetDisplayNearestWindow(browser2->window()->GetNativeWindow())
.id());
// Put the second browser into immersive fullscreen.
BrowserView* browser_view2 = BrowserView::GetBrowserViewForBrowser(browser2);
ImmersiveModeController* immersive_controller2 =
browser_view2->immersive_mode_controller();
chromeos::ImmersiveFullscreenControllerTestApi(
static_cast<ImmersiveModeControllerChromeos*>(immersive_controller2)
->controller())
.SetupForTest();
chrome::ToggleFullscreenMode(browser2);
// For MD, the browser's top chrome is completely offscreen, with tabstrip
// visible.
ASSERT_TRUE(immersive_controller2->IsEnabled());
ASSERT_FALSE(immersive_controller2->IsRevealed());
ASSERT_TRUE(tab_strip2->GetVisible());
// Move to the first tab and drag it enough so that it detaches, but not
// enough that it attaches to browser2.
DragTabAndNotify(tab_strip,
base::BindOnce(&DragTabToWindowInSeparateDisplayStep2, this,
tab_strip, tab_strip2));
// Should now be attached to tab_strip2.
ASSERT_TRUE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// browser2's top chrome should be revealed and the tab strip should be
// at normal height while user is tragging tabs_strip2's tabs.
ASSERT_TRUE(immersive_controller2->IsRevealed());
ASSERT_TRUE(tab_strip2->GetVisible());
// Release the mouse, stopping the drag session.
ASSERT_TRUE(ReleaseInput());
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 100", IDString(browser2->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
// Move the mouse off of browser2's top chrome.
ASSERT_TRUE(
ui_test_utils::SendMouseMoveSync(displays.first.bounds().CenterPoint()));
// The first browser window should not be in immersive fullscreen.
// browser2 should still be in immersive fullscreen, but the top chrome should
// no longer be revealed.
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
EXPECT_FALSE(browser_view->immersive_mode_controller()->IsEnabled());
EXPECT_TRUE(immersive_controller2->IsEnabled());
EXPECT_FALSE(immersive_controller2->IsRevealed());
EXPECT_TRUE(tab_strip2->GetVisible());
}
// Subclass of DetachToBrowserTabDragControllerTest that
// creates multiple displays with different device scale factors.
class DifferentDeviceScaleFactorDisplayTabDragControllerTest
: public DetachToBrowserTabDragControllerTest {
public:
DifferentDeviceScaleFactorDisplayTabDragControllerTest() {}
DifferentDeviceScaleFactorDisplayTabDragControllerTest(
const DifferentDeviceScaleFactorDisplayTabDragControllerTest&) = delete;
DifferentDeviceScaleFactorDisplayTabDragControllerTest& operator=(
const DifferentDeviceScaleFactorDisplayTabDragControllerTest&) = delete;
virtual ~DifferentDeviceScaleFactorDisplayTabDragControllerTest() {}
void SetUpCommandLine(base::CommandLine* command_line) override {
DetachToBrowserTabDragControllerTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII("ash-host-window-bounds",
"800x600,800+0-800x600*2");
}
float GetCursorDeviceScaleFactor() const {
return aura::client::GetCursorClient(
browser()->window()->GetNativeWindow()->GetRootWindow())
->GetCursor()
.image_scale_factor();
}
};
namespace {
// The points where a tab is dragged in CursorDeviceScaleFactorStep.
constexpr gfx::Point kDragPoints[] = {
{300, 200}, {399, 200}, {500, 200}, {400, 200}, {300, 200},
};
// The expected device scale factors after the cursor is moved to the
// corresponding kDragPoints in CursorDeviceScaleFactorStep.
constexpr float kDeviceScaleFactorExpectations[] = {
1.0f, 1.0f, 2.0f, 2.0f, 1.0f,
};
static_assert(
base::size(kDragPoints) == base::size(kDeviceScaleFactorExpectations),
"kDragPoints and kDeviceScaleFactorExpectations must have the same "
"number of elements");
// Drags tab to |kDragPoints[index]|, then calls the next step function.
void CursorDeviceScaleFactorStep(
DifferentDeviceScaleFactorDisplayTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
size_t index) {
SCOPED_TRACE(index);
ASSERT_FALSE(not_attached_tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
if (index > 0) {
EXPECT_EQ(kDragPoints[index - 1],
aura::Env::GetInstance()->last_mouse_location());
EXPECT_EQ(kDeviceScaleFactorExpectations[index - 1],
test->GetCursorDeviceScaleFactor());
}
if (index < base::size(kDragPoints)) {
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
kDragPoints[index], base::BindOnce(&CursorDeviceScaleFactorStep, test,
not_attached_tab_strip, index + 1)));
} else {
// Finishes a series of CursorDeviceScaleFactorStep calls and ends drag.
ASSERT_TRUE(
ui_test_utils::SendMouseEventsSync(ui_controls::LEFT, ui_controls::UP));
}
}
} // namespace
// Verifies cursor's device scale factor is updated when a tab is moved across
// displays with different device scale factors (http://crbug.com/154183).
// TODO(pkasting): In interactive_ui_tests, scale factor never changes to 2.
// https://crbug.com/918731
// TODO(pkasting): In non_single_process_mash_interactive_ui_tests, pointer is
// warped during the drag (which results in changing to scale factor 2 early),
// and scale factor doesn't change back to 1 at the end.
// https://crbug.com/918732
IN_PROC_BROWSER_TEST_P(DifferentDeviceScaleFactorDisplayTabDragControllerTest,
DISABLED_CursorDeviceScaleFactor) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
// Move the second browser to the second display.
ASSERT_EQ(2, display::Screen::GetScreen()->GetNumDisplays());
// Move to the first tab and drag it enough so that it detaches.
DragTabAndNotify(tab_strip, base::BindOnce(&CursorDeviceScaleFactorStep, this,
tab_strip, 0));
}
class DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest
: public DetachToBrowserTabDragControllerTest {
public:
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest() {}
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest(
const DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest&) =
delete;
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest& operator=(
const DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest&) =
delete;
void SetUpCommandLine(base::CommandLine* command_line) override {
DetachToBrowserTabDragControllerTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII("ash-host-window-bounds",
"0+0-800x600,800+0-800x600");
}
};
namespace {
// Invoked from the nested run loop.
void CancelDragTabToWindowInSeparateDisplayStep3(
TabStrip* tab_strip,
const BrowserList* browser_list) {
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, browser_list->size());
// Switching display mode should cancel the drag operation.
ash::ShellTestApi().AddRemoveDisplay();
}
// Invoked from the nested run loop.
void CancelDragTabToWindowInSeparateDisplayStep2(
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest* test,
TabStrip* tab_strip,
Display current_display,
gfx::Point final_destination,
const BrowserList* browser_list) {
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
EXPECT_EQ(
current_display.id(),
display::Screen::GetScreen()
->GetDisplayNearestWindow(new_browser->window()->GetNativeWindow())
.id());
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
final_destination,
base::BindOnce(&CancelDragTabToWindowInSeparateDisplayStep3, tab_strip,
browser_list)));
}
} // namespace
// Drags from browser to a second display and releases input.
IN_PROC_BROWSER_TEST_P(
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest,
CancelDragTabToWindowIn2ndDisplay) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
// Move the second browser to the second display.
const std::pair<Display, Display> displays =
GetDisplays(display::Screen::GetScreen());
gfx::Point final_destination = displays.second.work_area().CenterPoint();
// Move to the first tab and drag it enough so that it detaches, but not
// enough to move to another display.
DragTabAndNotify(tab_strip,
base::BindOnce(&CancelDragTabToWindowInSeparateDisplayStep2,
this, tab_strip, displays.first,
final_destination, browser_list));
ASSERT_EQ(1u, browser_list->size());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
// Release the mouse
ASSERT_TRUE(
ui_test_utils::SendMouseEventsSync(ui_controls::LEFT, ui_controls::UP));
}
// Drags from browser from a second display to primary and releases input.
IN_PROC_BROWSER_TEST_P(
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest,
CancelDragTabToWindowIn1stDisplay) {
display::Screen* screen = display::Screen::GetScreen();
const std::pair<Display, Display> displays = GetDisplays(screen);
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
EXPECT_EQ(
displays.first.id(),
screen->GetDisplayNearestWindow(browser()->window()->GetNativeWindow())
.id());
browser()->window()->SetBounds(displays.second.work_area());
EXPECT_EQ(
displays.second.id(),
screen->GetDisplayNearestWindow(browser()->window()->GetNativeWindow())
.id());
// Move the second browser to the display.
gfx::Point final_destination = displays.first.work_area().CenterPoint();
// Move to the first tab and drag it enough so that it detaches, but not
// enough to move to another display.
DragTabAndNotify(tab_strip,
base::BindOnce(&CancelDragTabToWindowInSeparateDisplayStep2,
this, tab_strip, displays.second,
final_destination, browser_list));
ASSERT_EQ(1u, browser_list->size());
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
// Release the mouse
ASSERT_TRUE(
ui_test_utils::SendMouseEventsSync(ui_controls::LEFT, ui_controls::UP));
}
// Subclass of DetachToBrowserTabDragControllerTest that runs tests only with
// touch input.
class DetachToBrowserTabDragControllerTestTouch
: public DetachToBrowserTabDragControllerTest {
public:
DetachToBrowserTabDragControllerTestTouch() {}
DetachToBrowserTabDragControllerTestTouch(
const DetachToBrowserTabDragControllerTestTouch&) = delete;
DetachToBrowserTabDragControllerTestTouch& operator=(
const DetachToBrowserTabDragControllerTestTouch&) = delete;
virtual ~DetachToBrowserTabDragControllerTestTouch() {}
void TearDown() override { ui::SetEventTickClockForTesting(nullptr); }
};
namespace {
void PressSecondFingerWhileDetachedStep3(
DetachToBrowserTabDragControllerTest* test) {
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, test->browser_list->size());
ASSERT_TRUE(test->browser_list->get(1)->window()->IsActive());
ASSERT_TRUE(test->ReleaseInput());
ASSERT_TRUE(test->ReleaseInput(1));
}
void PressSecondFingerWhileDetachedStep2(
DetachToBrowserTabDragControllerTest* test,
const gfx::Point& target_point) {
ASSERT_TRUE(TabDragController::IsActive());
ASSERT_EQ(2u, test->browser_list->size());
ASSERT_TRUE(test->browser_list->get(1)->window()->IsActive());
// Continue dragging after adding a second finger.
ASSERT_TRUE(test->PressInput(gfx::Point(), 1));
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
target_point,
base::BindOnce(&PressSecondFingerWhileDetachedStep3, test)));
}
} // namespace
// Detaches a tab and while detached presses a second finger.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestTouch,
PressSecondFingerWhileDetached) {
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
EXPECT_EQ("0 1", IDString(browser()->tab_strip_model()));
// Move to the first tab and drag it enough so that it detaches. Drag it
// slightly more horizontally so that it does not generate a swipe down
// gesture that minimizes the detached browser window.
const int touch_move_delta = GetDetachY(tab_strip);
const gfx::Point target = GetCenterInScreenCoordinates(tab_strip->tab_at(0)) +
gfx::Vector2d(0, 2 * touch_move_delta);
DragTabAndNotify(
tab_strip,
base::BindOnce(&PressSecondFingerWhileDetachedStep2, this, target), 0,
touch_move_delta + 5);
// Should no longer be dragging.
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
// There should now be another browser.
ASSERT_EQ(2u, browser_list->size());
Browser* new_browser = browser_list->get(1);
ASSERT_TRUE(new_browser->window()->IsActive());
TabStrip* tab_strip2 = GetTabStripForBrowser(new_browser);
ASSERT_FALSE(tab_strip2->GetDragContext()->IsDragSessionActive());
EXPECT_EQ("0", IDString(new_browser->tab_strip_model()));
EXPECT_EQ("1", IDString(browser()->tab_strip_model()));
}
namespace {
void SecondFingerPressTestStep3(DetachToBrowserTabDragControllerTest* test) {
ASSERT_TRUE(test->ReleaseInput());
}
void SecondFingerPressTestStep2(DetachToBrowserTabDragControllerTest* test,
TabStrip* not_attached_tab_strip,
TabStrip* target_tab_strip) {
ASSERT_TRUE(TabDragController::IsActive());
// And there should be three browser windows, including the newly created one
// for the dragged tab.
EXPECT_EQ(3u, test->browser_list->size());
// Put the window that accociated with |target_tab_strip| in overview.
test::GetWindowForTabStrip(target_tab_strip)
->SetProperty(chromeos::kIsShowingInOverviewKey, true);
// Drag to |target_tab_strip|.
const gfx::Point target_point =
GetCenterInScreenCoordinates(target_tab_strip);
ASSERT_TRUE(test->DragInputTo(target_point));
// Now add a second finger to tap on it.
aura::Env::GetInstance()->set_touch_down(true);
ASSERT_TRUE(test->PressInput(gfx::Point(), 1));
ASSERT_TRUE(test->ReleaseInput(1));
ASSERT_TRUE(test->DragInputToNotifyWhenDone(
target_point, base::BindOnce(&SecondFingerPressTestStep3, test)));
}
} // namespace
// Tests that when drgging a tab to a browser window that's currently in
// overview, press the second finger should not cause chrome crash.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestTouch,
SecondFingerPressTest) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Create another browser.
Browser* browser2 = CreateAnotherBrowserAndResize();
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.
DragTabAndNotify(tab_strip, base::BindOnce(&SecondFingerPressTestStep2, this,
tab_strip, tab_strip2));
// Test that after dragging there is no crash and the dragged tab should now
// be merged into the target tabstrip.
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(2u, browser_list->size());
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestTouch,
LeftSnapShouldntCauseMergeAtEnd) {
TabStrip* tab_strip = GetTabStripForBrowser(browser());
AddTabsAndResetBrowser(browser(), 1);
// Set the last mouse location at the center of tab 0. This shouldn't affect
// the touch behavior below. See https://crbug.com/914527#c1 for the details
// of how this can affect the result.
gfx::Point tab_0_center(GetCenterInScreenCoordinates(tab_strip->tab_at(0)));
base::RunLoop run_loop;
ui_controls::SendMouseMoveNotifyWhenDone(tab_0_center.x(), tab_0_center.y(),
run_loop.QuitClosure());
run_loop.Run();
// Drag the tab 1 to left-snapping.
DragTabAndNotify(
tab_strip, base::BindLambdaForTesting([&]() {
const gfx::Rect display_bounds =
display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
const gfx::Point target(display_bounds.x(),
display_bounds.CenterPoint().y());
ASSERT_TRUE(
DragInputToNotifyWhenDone(target, base::BindLambdaForTesting([&]() {
ASSERT_TRUE(ReleaseInput());
})));
}),
1);
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(2u, browser_list->size());
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestTouch,
FlingDownAtEndOfDrag) {
// Reduce the minimum fling velocity for this specific test case to cause the
// fling-down gesture in the middle of tab-dragging. This should end up with
// minimizing the window. See https://crbug.com/902897 for the details.
ui::GestureConfiguration::GetInstance()->set_min_fling_velocity(1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
test::QuitDraggingObserver observer(tab_strip);
const gfx::Point tab_0_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(0));
const gfx::Vector2d detach(0, GetDetachY(tab_strip));
base::SimpleTestTickClock clock;
clock.SetNowTicks(base::TimeTicks::Now());
ui::SetEventTickClockForTesting(&clock);
ASSERT_TRUE(PressInput(tab_0_center));
clock.Advance(base::TimeDelta::FromMilliseconds(5));
ASSERT_TRUE(DragInputToNotifyWhenDone(
tab_0_center + detach, base::BindLambdaForTesting([&]() {
// Drag down again; this should cause a fling-down event.
clock.Advance(base::TimeDelta::FromMilliseconds(5));
ASSERT_TRUE(DragInputToNotifyWhenDone(
tab_0_center + detach + detach, base::BindLambdaForTesting([&]() {
clock.Advance(base::TimeDelta::FromMilliseconds(5));
ASSERT_TRUE(ReleaseInput());
})));
})));
observer.Wait();
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_TRUE(browser()->window()->IsMinimized());
EXPECT_FALSE(browser()->window()->IsVisible());
ui::SetEventTickClockForTesting(nullptr);
}
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTestTouch,
FlingOnStartingDrag) {
ui::GestureConfiguration::GetInstance()->set_min_fling_velocity(1);
AddTabsAndResetBrowser(browser(), 1);
TabStrip* tab_strip = GetTabStripForBrowser(browser());
const gfx::Point tab_0_center =
GetCenterInScreenCoordinates(tab_strip->tab_at(0));
const gfx::Vector2d detach(0, GetDetachY(tab_strip));
// Sends events to the server without waiting for its reply, which will cause
// extra touch events before PerformWindowMove starts handling events.
test::QuitDraggingObserver observer(tab_strip);
base::SimpleTestTickClock clock;
clock.SetNowTicks(base::TimeTicks::Now());
ui::SetEventTickClockForTesting(&clock);
ASSERT_TRUE(PressInput(tab_0_center));
clock.Advance(base::TimeDelta::FromMilliseconds(5));
ASSERT_TRUE(DragInputToAsync(tab_0_center + detach));
clock.Advance(base::TimeDelta::FromMilliseconds(5));
ASSERT_TRUE(DragInputToAsync(tab_0_center + detach + detach));
clock.Advance(base::TimeDelta::FromMilliseconds(2));
ASSERT_TRUE(ReleaseInput());
observer.Wait();
ASSERT_FALSE(tab_strip->GetDragContext()->IsDragSessionActive());
ASSERT_FALSE(TabDragController::IsActive());
EXPECT_EQ(2u, browser_list->size());
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
namespace {
class SelectTabDuringDragObserver : public TabStripModelObserver {
public:
SelectTabDuringDragObserver() = default;
~SelectTabDuringDragObserver() override = default;
void OnTabStripModelChanged(
TabStripModel* tab_strip_model,
const TabStripModelChange& change,
const TabStripSelectionChange& selection) override {
if (change.type() != TabStripModelChange::kMoved)
return;
const TabStripModelChange::Move* move = change.GetMove();
int index_to_select = move->to_index == 0 ? 1 : 0;
tab_strip_model->ToggleSelectionAt(index_to_select);
}
};
} // namespace
// Bug fix for crbug.com/1196309. Don't change tab selection while dragging.
IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
SelectTabDuringDrag) {
TabStripModel* model = browser()->tab_strip_model();
TabStrip* tab_strip = GetTabStripForBrowser(browser());
SelectTabDuringDragObserver observer;
model->AddObserver(&observer);
AddTabsAndResetBrowser(browser(), 1);
ASSERT_EQ(2, model->count());
ASSERT_TRUE(PressInput(GetCenterInScreenCoordinates(tab_strip->tab_at(0))));
ASSERT_TRUE(DragInputTo(GetCenterInScreenCoordinates(tab_strip->tab_at(1))));
{
gfx::Rect tab_bounds = tab_strip->tab_at(1)->GetLocalBounds();
views::View::ConvertRectToScreen(tab_strip->tab_at(1), &tab_bounds);
ASSERT_TRUE(DragInputTo(tab_bounds.right_center()));
}
ASSERT_TRUE(ReleaseInput());
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
INSTANTIATE_TEST_SUITE_P(TabDragging,
DetachToBrowserTabDragControllerTest,
::testing::Values("mouse", "touch"));
INSTANTIATE_TEST_SUITE_P(
TabDragging,
DetachToBrowserTabDragControllerTestWithScrollableTabStripEnabled,
::testing::Values("mouse", "touch"));
INSTANTIATE_TEST_SUITE_P(TabDragging,
DetachToBrowserInSeparateDisplayTabDragControllerTest,
::testing::Values("mouse"));
INSTANTIATE_TEST_SUITE_P(TabDragging,
DifferentDeviceScaleFactorDisplayTabDragControllerTest,
::testing::Values("mouse"));
INSTANTIATE_TEST_SUITE_P(
TabDragging,
DetachToBrowserInSeparateDisplayAndCancelTabDragControllerTest,
::testing::Values("mouse"));
INSTANTIATE_TEST_SUITE_P(TabDragging,
DetachToBrowserTabDragControllerTestTouch,
::testing::Values("touch"));
INSTANTIATE_TEST_SUITE_P(
TabDragging,
DetachToBrowserTabDragControllerTestWithTabbedSystemApp,
::testing::Values("mouse", "touch"));
#else
INSTANTIATE_TEST_SUITE_P(TabDragging,
DetachToBrowserTabDragControllerTest,
::testing::Values("mouse"));
INSTANTIATE_TEST_SUITE_P(
TabDragging,
DetachToBrowserTabDragControllerTestWithScrollableTabStripEnabled,
::testing::Values("mouse"));
#endif