Commit ddb2b533 authored by Xiaoqian Dai's avatar Xiaoqian Dai Committed by Commit Bot

Tab Dragging: Fix occasional crash when the drag is ending.

When the drag ends, TabDragController::ClearTabDraggingInfo() might
trigger a bounds change of the dragged window, which then trigger
TabDragController::OnWidgetBoundsChanged() to be called again to
continue dragging. We should remove TabDragController as an observer of
the dragged widget when the drag is ending as we do not care about any
subsequent moves to the widget.

Bug: 854835
Change-Id: Ia940e7f0a33609bfc19ff9d96861948579fb5dd7
Reviewed-on: https://chromium-review.googlesource.com/1109266Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Xiaoqian Dai <xdai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#573521}
parent ad1f2f59
......@@ -276,10 +276,8 @@ TabDragController::~TabDragController() {
if (g_tab_drag_controller == this)
g_tab_drag_controller = NULL;
if (move_loop_widget_) {
if (added_observer_to_move_loop_widget_)
move_loop_widget_->RemoveObserver(this);
}
if (move_loop_widget_)
move_loop_widget_->RemoveObserver(this);
if (source_tabstrip_)
GetModel(source_tabstrip_)->RemoveObserver(this);
......@@ -1155,7 +1153,6 @@ void TabDragController::RunMoveLoop(const gfx::Vector2d& drag_offset) {
move_loop_widget_ = GetAttachedBrowserWidget();
DCHECK(move_loop_widget_);
move_loop_widget_->AddObserver(this);
added_observer_to_move_loop_widget_ = true;
is_dragging_window_ = true;
base::WeakPtr<TabDragController> ref(weak_factory_.GetWeakPtr());
if (can_release_capture_) {
......@@ -1411,7 +1408,14 @@ void TabDragController::EndDragImpl(EndDragType type) {
bring_to_front_timer_.Stop();
move_stacked_timer_.Stop();
ClearTabDraggingInfo();
if (move_loop_widget_) {
// This function is only called when the drag is ending. At this point we
// don't care about any subsequent moves to the widget, so we remove the
// observer. If we didn't do this we could get told the widget moved and
// attempt to do the wrong thing.
move_loop_widget_->RemoveObserver(this);
move_loop_widget_ = nullptr;
}
if (is_dragging_window_) {
waiting_for_run_loop_to_exit_ = true;
......@@ -1420,6 +1424,8 @@ void TabDragController::EndDragImpl(EndDragType type) {
GetAttachedBrowserWidget()->EndMoveLoop();
}
ClearTabDraggingInfo();
if (type != TAB_DESTROYED) {
// We only finish up the drag if we were actually dragging. If start_drag_
// is false, the user just clicked and released and didn't move the mouse
......@@ -1638,14 +1644,6 @@ void TabDragController::CompleteDrag() {
}
void TabDragController::MaximizeAttachedWindow() {
if (move_loop_widget_ && added_observer_to_move_loop_widget_) {
// This function is only called when the drag is ending. At this point we
// don't care about any subsequent moves to the widget, so we remove the
// observer. If we didn't do this we could get told the widget moved and
// attempt to do the wrong thing.
move_loop_widget_->RemoveObserver(this);
added_observer_to_move_loop_widget_ = false;
}
GetAttachedBrowserWidget()->Maximize();
#if defined(OS_CHROMEOS)
if (was_source_fullscreen_) {
......
......@@ -623,10 +623,6 @@ class TabDragController : public views::WidgetObserver,
// Non-null for the duration of RunMoveLoop.
views::Widget* move_loop_widget_;
// Whether TabDragController has been added an observer to
// |move_loop_widget_|. Only meaningful if |move_loop_widget_| is non-null.
bool added_observer_to_move_loop_widget_ = false;
// See description above getter.
bool is_mutating_;
......
......@@ -45,6 +45,7 @@
#include "content/public/browser/notification_source.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "ui/aura/env.h"
#include "ui/base/test/ui_controls.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
......@@ -535,6 +536,18 @@ class DetachToBrowserTabDragControllerTest
ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::UP);
}
bool MoveInputTo(const gfx::Point& location) {
aura::Env::GetInstance()->SetLastMouseLocation(location);
if (input_source() == INPUT_SOURCE_MOUSE)
return ui_test_utils::SendMouseMoveSync(location);
#if defined(OS_CHROMEOS)
event_generator_->set_current_location(location);
#else
NOTREACHED();
#endif
return true;
}
void QuitWhenNotDragging() {
if (input_source() == INPUT_SOURCE_MOUSE) {
// Schedule observer to quit message loop when done dragging. This has to
......@@ -2658,6 +2671,126 @@ IN_PROC_BROWSER_TEST_P(DetachToBrowserTabDragControllerTest,
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() 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 happend 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_;
DISALLOW_COPY_AND_ASSIGN(DraggedWindowObserver);
};
void DoNotObserveDraggedWidgetAfterDragEndsStep2(
DetachToBrowserTabDragControllerTest* test,
DraggedWindowObserver* observer,
TabStrip* attached_tab_strip) {
ASSERT_TRUE(attached_tab_strip->IsDragSessionActive());
ASSERT_TRUE(TabDragController::IsActive());
// Start observe the dragged window.
observer->StartObserving(attached_tab_strip->GetWidget()->GetNativeWindow());
if (test->input_source() == INPUT_SOURCE_TOUCH)
ASSERT_TRUE(test->ReleaseInput());
else
ASSERT_TRUE(test->ReleaseMouseAsync());
}
} // 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 = CreateAnotherWindowBrowserAndRelayout();
TabStrip* tab_strip2 = GetTabStripForBrowser(browser2);
EXPECT_EQ(2u, browser_list->size());
// Create an window observer to observe the dragged window.
gfx::Point target_point(tab_strip2->bounds().CenterPoint());
views::View::ConvertPointToScreen(tab_strip2, &target_point);
std::unique_ptr<DraggedWindowObserver> observer(new DraggedWindowObserver(
this, tab_strip->GetWidget()->GetNativeWindow(),
tab_strip2->GetWidget()->GetNativeWindow()->bounds(), target_point));
// Drag the tab long enough so that it moves.
gfx::Point tab_0_center(GetCenterInScreenCoordinates(tab_strip->tab_at(0)));
ASSERT_TRUE(PressInput(tab_0_center));
ASSERT_TRUE(DragInputToNotifyWhenDone(
tab_0_center.x(), tab_0_center.y() + GetDetachY(tab_strip),
base::BindOnce(&DoNotObserveDraggedWidgetAfterDragEndsStep2, this,
observer.get(), tab_strip)));
QuitWhenNotDragging();
// 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());
}
#endif // OS_CHROMEOS
#if defined(OS_CHROMEOS)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment