tabdrag: Fix tabstrip layout when dragging out of a maximized window

When a tab is dragged out of a maximized window, the detached window is
created in restored state (ie: with different geometry) and tab drag
controller needs to adjust the window and dragged tabs' bounds (more
specifically x location), so that they appear under the pointer. That's
done at TabStrip::TabDragContextImpl::SetBoundsForDrag() function [2],
which calls into Views::SetBoundsRect() for each selected Tab view. It
turns out that it is not enough to ensure the tabstrip and its parent,
TabStripRegionView, get properly re-laid out, leading to issues like
[1], where the dragged tab gets partially or totally occluded by the
TabStripRegionView's child views.

To fix it, this CL adds layout invalidation for tab strip just after
setting bounds rect for each dragged tabs, ensuring both tab strip and
its parent views are correctly relaid out.

Bug: 1151092

[1] https://drive.google.com/file/d/1a6kEVy0DNUo9TTeiCYjkpf7d42TI3T7n
[2] https://source.chromium.org/chromium/chromium/src/+/master:chrome/browser/ui/views/tabs/tab_strip.cc;l=697-708;drc=d81c5852498699fe3cd812e78d31c77c28e29281

R=sky@chromium.org

Change-Id: I51244ad4485856334ce3443ed92ce80e2fad2cd1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2572040
Commit-Queue: Nick Yamane <nickdiego@igalia.com>
Commit-Queue: Scott Violet <sky@chromium.org>
Reviewed-by: Taylor Bergquist <tbergquist@chromium.org>
Cr-Commit-Position: refs/heads/master@{#843781}
diff --git a/chrome/browser/ui/views/tabs/tab_strip.cc b/chrome/browser/ui/views/tabs/tab_strip.cc
index 6358ef8..38ac213 100644
--- a/chrome/browser/ui/views/tabs/tab_strip.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip.cc
@@ -712,6 +712,12 @@
     DCHECK_EQ(views.size(), bounds.size());
     for (size_t i = 0; i < views.size(); ++i)
       views[i]->SetBoundsRect(bounds[i]);
+
+    // Ensure that the tab strip and its parent views are correctly re-laid out
+    // after repositioning dragged tabs. This avoids visual/layout issues such
+    // as https://crbug.com/1151092.
+    tab_strip_->PreferredSizeChanged();
+
     // Reset the layout size as we've effectively laid out a different size.
     // This ensures a layout happens after the drag is done.
     tab_strip_->last_layout_size_ = gfx::Size();
diff --git a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
index a6a4cc4..cfd1522 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_unittest.cc
@@ -40,6 +40,7 @@
 #include "ui/views/test/ax_event_counter.h"
 #include "ui/views/view.h"
 #include "ui/views/view_class_properties.h"
+#include "ui/views/view_observer.h"
 #include "ui/views/view_targeter.h"
 #include "ui/views/view_utils.h"
 #include "ui/views/widget/widget.h"
@@ -1368,4 +1369,47 @@
   EXPECT_EQ(1, tab_strip_->GetModelCount());
 }
 
+namespace {
+
+struct SizeChangeObserver : public views::ViewObserver {
+  explicit SizeChangeObserver(views::View* observed_view)
+      : view(observed_view) {
+    view->AddObserver(this);
+  }
+  ~SizeChangeObserver() override { view->RemoveObserver(this); }
+
+  void OnViewPreferredSizeChanged(views::View* observed_view) override {
+    size_change_count++;
+  }
+
+  views::View* const view;
+  int size_change_count = 0;
+};
+
+}  // namespace
+
+// When dragged tabs' bounds are modified through TabDragContext, both tab strip
+// and its parent view must get re-laid out http://crbug.com/1151092.
+TEST_P(TabStripTest, RelayoutAfterDraggedTabBoundsUpdate) {
+  SetMaxTabStripWidth(400);
+
+  // Creates a single tab.
+  controller_->CreateNewTab();
+  CompleteAnimationAndLayout();
+
+  int dragged_tab_index = controller_->GetActiveIndex();
+  Tab* dragged_tab = tab_strip_->tab_at(dragged_tab_index);
+  ASSERT_TRUE(dragged_tab);
+
+  // Mark the active tab as being dragged.
+  dragged_tab->set_dragging(true);
+
+  constexpr int kXOffset = 20;
+  std::vector<TabSlotView*> tabs{dragged_tab};
+  std::vector<gfx::Rect> bounds{gfx::Rect({kXOffset, 0}, dragged_tab->size())};
+  SizeChangeObserver view_observer(tab_strip_);
+  tab_strip_->GetDragContext()->SetBoundsForDrag(tabs, bounds);
+  EXPECT_EQ(1, view_observer.size_change_count);
+}
+
 INSTANTIATE_TEST_SUITE_P(All, TabStripTest, ::testing::Values(false, true));