Commit 41f6e707 authored by Collin Baker's avatar Collin Baker Committed by Commit Bot

Split slot index calculation into separate functions

The original GetSlotIndexForTabModelIndex served two purposes: getting
the slot for an existing tab, and getting the insertion index for a
new slot. This splits it into separate methods with common logic
remaining in a shared method.

The logic had a bug when requesting the first tab of a group while
tabs just before it are animating closed. This bug is fixed.

To help debug the associated crash, CHECKs are added.

Bug: 1138748
Change-Id: I6e0d797bb70e9aa1da813dbf1fecf228290e55b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2515363
Commit-Queue: Collin Baker <collinbaker@chromium.org>
Reviewed-by: default avatarTaylor Bergquist <tbergquist@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824672}
parent 8350dd0f
......@@ -949,7 +949,7 @@ class TabStrip::TabDragContextImpl : public TabDragContext {
if (dragging_group == other_group) {
// |dragging_tabs| can only be empty if dragging in from another window,
// in which case |dragging_group| can't be the same as |other_group|.
DCHECK(dragging_tabs.size() > 0);
DCHECK_GT(dragging_tabs.size(), 0u);
if (candidate_index <= dragging_tabs.front() ||
dragging_tabs.back() >= GetTabCount() - 1)
return dragging_tabs.front();
......@@ -1010,8 +1010,6 @@ TabStrip::TabStrip(std::unique_ptr<TabStripController> controller)
layout_helper_(std::make_unique<TabStripLayoutHelper>(
controller_.get(),
base::BindRepeating(&TabStrip::tabs_view_model,
base::Unretained(this)),
base::BindRepeating(&TabStrip::GetGroupHeaders,
base::Unretained(this)))),
drag_context_(std::make_unique<TabDragContextImpl>(this)) {
Init();
......
......@@ -5,7 +5,6 @@
#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_LAYOUT_HELPER_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_STRIP_LAYOUT_HELPER_H_
#include <map>
#include <vector>
#include "base/callback.h"
......@@ -33,12 +32,9 @@ class TabGroupId;
class TabStripLayoutHelper {
public:
using GetTabsCallback = base::RepeatingCallback<views::ViewModelT<Tab>*()>;
using GetGroupHeadersCallback = base::RepeatingCallback<
std::map<tab_groups::TabGroupId, TabGroupHeader*>()>;
TabStripLayoutHelper(const TabStripController* controller,
GetTabsCallback get_tabs_callback,
GetGroupHeadersCallback get_group_headers_callback);
GetTabsCallback get_tabs_callback);
TabStripLayoutHelper(const TabStripLayoutHelper&) = delete;
TabStripLayoutHelper& operator=(const TabStripLayoutHelper&) = delete;
~TabStripLayoutHelper();
......@@ -132,12 +128,26 @@ class TabStripLayoutHelper {
std::vector<gfx::Rect> CalculateIdealBounds(
base::Optional<int> available_width);
// Given a tab's |model_index| and |group|, returns the index of its
// corresponding TabSlot in |slots_|.
int GetSlotIndexForTabModelIndex(
int model_index,
// Given |model_index| for a tab already present in |slots_|, return
// the corresponding index in |slots_|.
int GetSlotIndexForExistingTab(int model_index) const;
// For a new tab at |new_model_index|, get the insertion index in
// |slots_|. |group| is the new tab's group.
int GetSlotInsertionIndexForNewTab(
int new_model_index,
base::Optional<tab_groups::TabGroupId> group) const;
// Used internally in the above two functions. For a tabstrip with N
// tabs, this takes 0 <= |model_index| <= N and returns the first
// possible slot corresponding to this model index.
//
// This means that if |model_index| is the first tab in a group, the
// returned slot index will point to the group header. For other tabs,
// the slot index corresponding to that tab will be returned. Finally,
// if |model_index| = N, slots_.size() will be returned.
int GetFirstSlotIndexForTabModelIndex(int model_index) const;
// Given a group ID, returns the index of its header's corresponding TabSlot
// in |slots_|.
int GetSlotIndexForGroupHeader(tab_groups::TabGroupId group) const;
......@@ -164,9 +174,8 @@ class TabStripLayoutHelper {
// The owning tabstrip's controller.
const TabStripController* const controller_;
// Callbacks to get the necessary View objects from the owning tabstrip.
// Callback to get the necessary View objects from the owning tabstrip.
GetTabsCallback get_tabs_callback_;
GetGroupHeadersCallback get_group_headers_callback_;
// Current collation of tabs and group headers, along with necessary data to
// run layout and animations for those Views.
......
......@@ -1348,4 +1348,55 @@ TEST_P(TabStripTest, DISABLED_NewTabButtonFlushWithTopOfTabStrip) {
// EXPECT_EQ(0, tab_strip_->new_tab_button()->bounds().y());
}
// Regression test for a crash when closing a tab under certain
// conditions. If the first tab in a group was animating closed,
// attempting to close the next tab could result in a crash. This was
// due to TabStripLayoutHelper mistakenly mapping the next tab's model
// index to the closing tab's slot. See https://crbug.com/1138748 for a
// related crash.
TEST_P(TabStripTest, CloseTabInGroupWhilePreviousTabAnimatingClosed) {
controller_->AddTab(0, true);
controller_->AddTab(1, false);
controller_->AddTab(2, false);
auto group_id = tab_groups::TabGroupId::GenerateNew();
controller_->MoveTabIntoGroup(1, group_id);
controller_->MoveTabIntoGroup(2, group_id);
CompleteAnimationAndLayout();
ASSERT_EQ(3, tab_strip_->tab_count());
ASSERT_EQ(3, tab_strip_->GetModelCount());
EXPECT_EQ(base::nullopt, tab_strip_->tab_at(0)->group());
EXPECT_EQ(group_id, tab_strip_->tab_at(1)->group());
EXPECT_EQ(group_id, tab_strip_->tab_at(2)->group());
// We have the following tabs:
// 1. An ungrouped tab with model index 0
// 2. A tab in |group_id| with model index 1
// 3. A tab in |group_id| with model index 2
controller_->RemoveTab(1);
// After closing the first tab, we now have:
// 1. An ungrouped tab with model index 0
// 2. A closing tab in |group_id| with no model index
// 3. A tab in |group_id| with model index 1.
//
// Closing the tab at model index 1 should result in (3) above being
// closed.
controller_->RemoveTab(1);
// We should now have:
// 1. An ungrouped tab with model index 0
// 2. A closing tab in |group_id| with no model index
// 3. A closing tab in |group_id| with no model index.
CompleteAnimationAndLayout();
// After finishing animations, there should be exactly 1 tab in no
// group.
EXPECT_EQ(1, tab_strip_->tab_count());
EXPECT_EQ(base::nullopt, tab_strip_->tab_at(0)->group());
EXPECT_EQ(1, tab_strip_->GetModelCount());
}
INSTANTIATE_TEST_SUITE_P(All, TabStripTest, ::testing::Values(false, true));
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