From fa7fa6e14541a10ae1a976f4d10341f72fa9d422 Mon Sep 17 00:00:00 2001 From: Sirisha Kavuluru Date: Tue, 9 Aug 2022 18:01:05 +0000 Subject: [PATCH] [TabGroup] Refocus previously selected tab on undo close Demo: https://drive.google.com/file/d/1GJLFU9FRgRPvhnruaS1ZnBGyBsAIYWUw/view?usp=sharing Bug: 1323356 Change-Id: Id08cd519e41319860c9082a091e7fb10d37317bd Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3803965 Commit-Queue: Sirisha Kavuluru Reviewed-by: Theresa Sullivan Reviewed-by: Neil Coronado Reviewed-by: Mei Liang Cr-Commit-Position: refs/heads/main@{#1033101} --- .../tasks/tab_management/TabListMediator.java | 23 +++ .../chrome/browser/UndoRefocusHelper.java | 82 ++++++--- .../chrome/browser/tabmodel/TabModelImpl.java | 1 + .../chrome/browser/UndoRefocusHelperTest.java | 170 ++++++++++++------ .../tasks/tab_groups/TabGroupModelFilter.java | 11 ++ .../browser/tabmodel/TabModelFilter.java | 26 +++ .../browser/tabmodel/TabModelObserver.java | 10 ++ 7 files changed, 235 insertions(+), 88 deletions(-) diff --git a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java index 788e94c5e9618e..cbb6eb11d09862 100644 --- a/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java +++ b/chrome/android/features/tab_ui/java/src/org/chromium/chrome/browser/tasks/tab_management/TabListMediator.java @@ -9,6 +9,7 @@ import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.CARD_TYPE; import static org.chromium.chrome.browser.tasks.tab_management.TabListModel.CardProperties.ModelType.TAB; import static org.chromium.chrome.browser.tasks.tab_management.TabProperties.CLOSE_BUTTON_DESCRIPTION_STRING; +import static org.chromium.chrome.browser.tasks.tab_management.TabProperties.TAB_ID; import android.app.Activity; import android.content.ComponentCallbacks; @@ -599,6 +600,22 @@ public void didSelectTab(Tab tab, int type, int lastId) { } int newIndex = mModel.indexFromId(tab.getId()); + if (newIndex == TabModel.INVALID_TAB_INDEX && mActionsOnAllRelatedTabs + && type == TabSelectionType.FROM_UNDO) { + // If a tab in tab group does not exist in model and needs to be selected from + // undo, identify the related TabIds and determine newIndex based on if any of + // the related ids are present in model. + List relatedTabIds = getRelatedTabsIds(tab.getId()); + if (!relatedTabIds.isEmpty()) { + for (int i = 0; i < mModel.size(); i++) { + int modelTabId = mModel.get(i).model.get(TAB_ID); + if (relatedTabIds.contains(modelTabId)) { + newIndex = i; + break; + } + } + } + } if (newIndex == TabModel.INVALID_TAB_INDEX) return; mModel.get(newIndex).model.set(TabProperties.IS_SELECTED, true); if (mThumbnailProvider != null && mVisible) { @@ -1079,6 +1096,12 @@ private List getRelatedTabsForId(int id) { return filter == null ? new ArrayList<>() : filter.getRelatedTabList(id); } + private List getRelatedTabsIds(int id) { + TabModelFilter filter = + mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter(); + return filter == null ? new ArrayList<>() : filter.getRelatedTabIds(id); + } + private int getIndexOfTab(Tab tab, boolean onlyShowRelatedTabs) { int index = TabList.INVALID_TAB_INDEX; if (tab == null) return index; diff --git a/chrome/android/java/src/org/chromium/chrome/browser/UndoRefocusHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/UndoRefocusHelper.java index 0b629e82057621..b875fcf7d0f65a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/UndoRefocusHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/UndoRefocusHelper.java @@ -25,6 +25,7 @@ import org.chromium.ui.base.DeviceFormFactor; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -39,7 +40,7 @@ public class UndoRefocusHelper implements DestroyObserver { private LayoutStateProvider.LayoutStateObserver mLayoutStateObserver; private TabModelSelectorTabModelObserver mTabModelSelectorTabModelObserver; private Integer mSelectedTabIdWhenTabClosed; - private Boolean mClosedAllTabs; + private boolean mWillCloseMultipleTabs; private boolean mTabSwitcherActive; private Callback mLayoutManagerSupplierCallback; private boolean mIsTablet; @@ -89,24 +90,14 @@ private void observeTabModel() { mTabModelSelectorTabModelObserver = new TabModelSelectorTabModelObserver(mModelSelector) { @Override public void willCloseTab(Tab tab, boolean animate) { - // TODO(crbug.com/1324405) Extract common logic between this method and - // willCloseAllTabs into a helper method. - if (mClosedAllTabs != null || tab.isIncognito()) return; + if (mWillCloseMultipleTabs || tab.isIncognito()) return; int tabId = tab.getId(); - TabModel model = mModelSelector.getModel(false); - if (!mTabSwitcherActive && mIsTablet) { mTabsClosedFromTabStrip.add(tabId); } - int selTabIndex = model.index(); - if (selTabIndex > -1 && selTabIndex < model.getCount()) { - Tab selectedTab = model.getTabAt(selTabIndex); - if (selectedTab != null && tabId == selectedTab.getId()) { - mSelectedTabIdWhenTabClosed = tabId; - } - } + maybeSetSelectedTabId(tab); } @Override @@ -119,28 +110,48 @@ public void didSelectTab(Tab tab, int type, int lastId) { } } + // TODO (crbug.com/1351406) Fix case of undo closing multiple tabs followed by single + // tab. @Override - public void willCloseAllTabs(boolean incognito) { - if (!incognito) { - TabModel model = mModelSelector.getCurrentModel(); - int selTabIndex = model.index(); - mClosedAllTabs = true; - if (selTabIndex > -1 && selTabIndex < model.getCount()) { - Tab tab = model.getTabAt(selTabIndex); - if (tab != null) { - mSelectedTabIdWhenTabClosed = tab.getId(); - if (!mTabSwitcherActive && mIsTablet) { - mTabsClosedFromTabStrip.add(tab.getId()); - } - } + public void willCloseMultipleTabs(boolean allowUndo, List tabs) { + if (!allowUndo || tabs.size() < 1) return; + mWillCloseMultipleTabs = true; + + // Record metric only once for the set. + // Use the first id to track the set. + if (!mTabSwitcherActive && mIsTablet) { + mTabsClosedFromTabStrip.add(tabs.get(0).getId()); + } + for (Tab tab : tabs) { + if (maybeSetSelectedTabId(tab)) { + break; } } } + @Override + public void willCloseAllTabs(boolean incognito) { + if (incognito) return; + int selectedTabIdx = mModelSelector.getModel(false).index(); + Tab selectedTab = mModelSelector.getModel(false).getTabAt(selectedTabIdx); + maybeSetSelectedTabId(selectedTab); + mWillCloseMultipleTabs = true; + // Record metric only once for the set. + // Use the selected id to track the set. + if (!mTabSwitcherActive && mIsTablet) { + mTabsClosedFromTabStrip.add(selectedTab.getId()); + } + } + @Override public void tabClosureUndone(Tab tab) { - if (mClosedAllTabs != null) return; int id = tab.getId(); + if (mWillCloseMultipleTabs) { + // allTabsClosureUndone does not receive set of Ids to pass to record method. + // Hence we record here first. + recordClosureCancellation(id); + return; + } recordClosureCancellation(id); if (mSelectedTabIdWhenTabClosed != null && mSelectedTabIdWhenTabClosed == id) { selectPreviouslySelectedTab(); @@ -150,7 +161,6 @@ public void tabClosureUndone(Tab tab) { @Override public void allTabsClosureUndone() { if (mSelectedTabIdWhenTabClosed != null) { - recordClosureCancellation(mSelectedTabIdWhenTabClosed); selectPreviouslySelectedTab(); } @@ -174,6 +184,20 @@ public void allTabsClosureCommitted(boolean isIncognito) { } } + private boolean maybeSetSelectedTabId(Tab tab) { + TabModel model = mModelSelector.getModel(false); + int tabId = tab.getId(); + int selTabIndex = model.index(); + if (selTabIndex > -1 && selTabIndex < model.getCount()) { + Tab selectedTab = model.getTabAt(selTabIndex); + if (selectedTab != null && tabId == selectedTab.getId()) { + mSelectedTabIdWhenTabClosed = tabId; + return true; + } + } + return false; + } + private void recordClosureCancellation(int id) { // Only record the action if the tab was previously closed from the tab strip. if (mTabsClosedFromTabStrip.contains(id)) { @@ -226,8 +250,8 @@ private void selectPreviouslySelectedTab() { * are reset so the next undo closure action does not reselect the reopened tab. */ private void resetSelectionsForUndo() { + mWillCloseMultipleTabs = false; mSelectedTabIdWhenTabClosed = null; - mClosedAllTabs = null; } @VisibleForTesting diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java index b12a736c48a89c..c143c5bdecd657 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelImpl.java @@ -460,6 +460,7 @@ public void closeMultipleTabs(List tabs, boolean canUndo) { if (!allowUndo) { notifyOnFinishingMultipleTabClosure(tabs); } + for (TabModelObserver obs : mObservers) obs.willCloseMultipleTabs(allowUndo, tabs); for (Tab tab : tabs) { closeTab(tab, null, false, false, canUndo, false, false); } diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java index 95d737c99bb0e6..ce5dc902166a83 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/UndoRefocusHelperTest.java @@ -23,16 +23,14 @@ import org.chromium.base.metrics.UmaRecorderHolder; import org.chromium.base.supplier.ObservableSupplier; import org.chromium.base.test.BaseRobolectricTestRunner; -import org.chromium.base.test.util.Feature; import org.chromium.chrome.browser.compositor.layouts.LayoutManagerImpl; import org.chromium.chrome.browser.compositor.overlays.strip.TestTabModel; -import org.chromium.chrome.browser.flags.ChromeFeatureList; import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.TabSelectionType; import org.chromium.chrome.browser.tabmodel.TabModelSelector; import org.chromium.chrome.browser.tabmodel.TabModelSelectorTabModelObserver; -import org.chromium.chrome.test.util.browser.Features; +import java.util.Arrays; import java.util.List; import java.util.concurrent.TimeoutException; @@ -41,7 +39,6 @@ * percentage of tabs used. */ @RunWith(BaseRobolectricTestRunner.class) -@Features.EnableFeatures(ChromeFeatureList.TAB_STRIP_IMPROVEMENTS) @Config(manifest = Config.NONE) public class UndoRefocusHelperTest { @Mock @@ -53,6 +50,10 @@ public class UndoRefocusHelperTest { private static final String UNDO_CLOSE_TAB_USER_ACTION = "TabletTabStrip.UndoCloseTab"; private final TestTabModel mModel = new TestTabModel(); + private final Tab mTab0 = getMockedTab(0); + private final Tab mTab1 = getMockedTab(1); + private final Tab mTab2 = getMockedTab(2); + private final Tab mTab3 = getMockedTab(3); private UndoRefocusHelper mUndoRefocusHelper; @@ -79,8 +80,7 @@ private void initializeTabModel(int selectedIndex) { public void tearDown() {} @Test - @Feature("Tab Strip Improvements") - public void testUndoTabClose_SelectedTab_ReselectsTab() { + public void testUndoSingleTabClose_SelectedTab_ReSelectsTab() { // Arrange: Start with fourth tab as selected index initializeTabModel(3); TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = @@ -99,8 +99,7 @@ public void testUndoTabClose_SelectedTab_ReselectsTab() { } @Test - @Feature("Tab Strip Improvements") - public void testUndoTabClose_UnSelectedTab_DoesNotSelectTab() { + public void testUndoSingleTabClose_UnSelectedTab_DoesNotSelectTab() { // Arrange: Initialize tabs with third tab selected. initializeTabModel(2); TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = @@ -119,55 +118,28 @@ public void testUndoTabClose_UnSelectedTab_DoesNotSelectTab() { } @Test - @Feature("Tab Strip Improvements") - public void testUndoAllTabsClose_ReselectsSelectedTab() { + public void testUndoMultipleSingleTabsClosed_ThenUndoSingleTabClose_ReSelectsTab() { // Arrange: Start with fourth tab as selected index. initializeTabModel(3); TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); - // Act: Close all tabs and undo closure. - tabModelSelectorTabModelObserver.willCloseAllTabs(false); - cancelAllTabClosure(tabModelSelectorTabModelObserver); - // Finalize closure cancellation completion. - tabModelSelectorTabModelObserver.allTabsClosureUndone(); - - // Assert: Fourth tab is selected after undo. - assertEquals(3, mModel.index()); - } - - @Test - @Feature("Tab Strip Improvements") - public void testUndoAllTabsClosed_ThenUndoSingleTabClose_ReselectsTab() { - // Arrange: Start with fourth tab as selected index. - initializeTabModel(3); - TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = - mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); - Tab tab = getMockedTab(3); - - // Act: Close all tabs and undo closure. - tabModelSelectorTabModelObserver.willCloseAllTabs(false); - cancelAllTabClosure(tabModelSelectorTabModelObserver); - // Finalize closure cancellation completion. - tabModelSelectorTabModelObserver.allTabsClosureUndone(); - - // Assert: Fourth tab is selected after undo. - assertEquals(3, mModel.index()); - - // Act 2: Close just the fourth tab and undo. - tabModelSelectorTabModelObserver.willCloseTab(tab, false); - // After fourth tab is closed, the third one should be selected. + // Act: Close multiple tabs one after the other including selected tab and undo closure + // once. + tabModelSelectorTabModelObserver.willCloseTab(mTab3, true); + // tab2 is selected after tab3 is closed. mModel.setIndex(2); - // Undo tab closure. - tabModelSelectorTabModelObserver.tabClosureUndone(tab); + tabModelSelectorTabModelObserver.willCloseTab(mTab2, true); - // Assert: Third tab is selected after undo. - assertEquals(3, mModel.index()); + // Last closure (mTab3) is undone + tabModelSelectorTabModelObserver.tabClosureUndone(mTab2); + + // Assert: mTab3 tab is selected after undo. + assertEquals(mTab2.getId(), mModel.getTabAt(mModel.index()).getId()); } @Test - @Feature("Tab Strip Improvements") - public void testUndoSingleTabClose_ThenUndoAllTabsClosed_ReselectsTab() { + public void testUndoSingleTabClose_ThenUndoMultipleTabsClosed_ReSelectsTab() { // Arrange: Start with fourth tab as selected index initializeTabModel(3); TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = @@ -184,9 +156,10 @@ public void testUndoSingleTabClose_ThenUndoAllTabsClosed_ReselectsTab() { // Assert: Fourth tab is selected after undo. assertEquals(3, mModel.index()); - // Act 2: Close all tabs and undo closure - tabModelSelectorTabModelObserver.willCloseAllTabs(false); - cancelAllTabClosure(tabModelSelectorTabModelObserver); + // Act 2: Close multiple tabs and undo closure + List multipleTabs = Arrays.asList(mTab2, mTab3); + tabModelSelectorTabModelObserver.willCloseMultipleTabs(true, multipleTabs); + cancelTabsClosure(tabModelSelectorTabModelObserver, multipleTabs); // Finalize closure cancellation completion. tabModelSelectorTabModelObserver.allTabsClosureUndone(); @@ -195,7 +168,6 @@ public void testUndoSingleTabClose_ThenUndoAllTabsClosed_ReselectsTab() { } @Test - @Feature("Tab Strip Improvements") public void testUndoSingleTabClose_AfterManualTabReselection_DoesNotReselectTab() { // Arrange: Start with fourth tab as selected index initializeTabModel(3); @@ -219,7 +191,6 @@ public void testUndoSingleTabClose_AfterManualTabReselection_DoesNotReselectTab( } @Test - @Feature("Tab Strip Improvements") public void testUndoSingleTabClose_AfterClosingSelectedTabs_ReselectsMostRecentlyClosedTab() { // Arrange: Start with fourth tab as selected index initializeTabModel(3); @@ -305,15 +276,96 @@ public void testUndoTabClose_TabSwitcherAndTabStrip_RecordsUserAction() { } @Test - public void testUndoAllTabClose_RecordsSingleUserAction() { + public void testUndoMultipleTabsClosedTogether_ReSelectsSelectedTab() { + // Arrange: Start with fourth tab as selected index. + initializeTabModel(3); + TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = + mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); + + // Act: Close multiple tabs including selected tab and undo closure. + List tabsToClose = Arrays.asList(mTab2, mTab3); + tabModelSelectorTabModelObserver.willCloseMultipleTabs(true, tabsToClose); + cancelTabsClosure(tabModelSelectorTabModelObserver, tabsToClose); + // Finalize closure cancellation completion. + tabModelSelectorTabModelObserver.allTabsClosureUndone(); + + // Assert: Fourth tab is selected after undo. + assertEquals(3, mModel.index()); + } + + @Test + public void testUndoManyMultipleTabsClosedTogether_ReSelectsSelectedTab() { + // Arrange: Start with fourth tab as selected index. + initializeTabModel(3); + TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = + mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); + + // Act: Close first set multiple tabs including selected tabs. + List tabsToClose1 = Arrays.asList(mTab2, mTab3); + tabModelSelectorTabModelObserver.willCloseMultipleTabs(true, tabsToClose1); + // Set mTab1 as newly selected tab. + mModel.setIndex(1); + // Act: Close second set multiple tabs including selected tabs. + List tabsToClose2 = Arrays.asList(mTab0, mTab1); + tabModelSelectorTabModelObserver.willCloseMultipleTabs(true, tabsToClose2); + + cancelTabsClosure(tabModelSelectorTabModelObserver, tabsToClose2); + cancelTabsClosure(tabModelSelectorTabModelObserver, tabsToClose1); + + // Finalize closure cancellation completion. + tabModelSelectorTabModelObserver.allTabsClosureUndone(); + + // Assert: mTab1 tab is selected after undo. + assertEquals(1, mModel.index()); + } + + @Test + public void testUndoMultipleTabClose_RecordsUserAction() { // Arrange: Start with fourth tab as selected index initializeTabModel(3); + TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = + mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); + // Act: Close multiple tabs and undo. + List multipleTabs = Arrays.asList(mTab2, mTab3); + tabModelSelectorTabModelObserver.willCloseMultipleTabs(true, multipleTabs); + cancelTabsClosure(tabModelSelectorTabModelObserver, multipleTabs); + tabModelSelectorTabModelObserver.allTabsClosureUndone(); + + // Assert: User action is recorded exactly once. + Mockito.verify(mUmaRecorder, Mockito.times(1)) + .recordUserAction(Mockito.eq(UNDO_CLOSE_TAB_USER_ACTION), Mockito.anyLong()); + } + + @Test + public void testUndoAllTabsClosedTogether_ReSelectsSelectedTab() { + // Arrange: Start with fourth tab as selected index. + initializeTabModel(3); + TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = + mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); + + // Act: Close all tabs and undo closure. + tabModelSelectorTabModelObserver.willCloseAllTabs(false); + cancelTabsClosure( + tabModelSelectorTabModelObserver, Arrays.asList(mTab0, mTab1, mTab2, mTab3)); + // Finalize closure cancellation completion. + tabModelSelectorTabModelObserver.allTabsClosureUndone(); + + // Assert: Fourth tab is selected after undo. + assertEquals(3, mModel.index()); + } + + @Test + public void testUndoAllTabsClosedTogether_RecordUserAction() { + // Arrange: Start with fourth tab as selected index. + initializeTabModel(3); TabModelSelectorTabModelObserver tabModelSelectorTabModelObserver = mUndoRefocusHelper.getTabModelSelectorTabModelObserverForTests(); - // Act: Close all tabs and undo. + // Act: Close all tabs and undo closure. tabModelSelectorTabModelObserver.willCloseAllTabs(false); - cancelAllTabClosure(tabModelSelectorTabModelObserver); + cancelTabsClosure( + tabModelSelectorTabModelObserver, Arrays.asList(mTab0, mTab1, mTab2, mTab3)); + // Finalize closure cancellation completion. tabModelSelectorTabModelObserver.allTabsClosureUndone(); // Assert: User action is recorded exactly once. @@ -346,10 +398,10 @@ private Tab getMockedTab(int id) { return tab1; } - private void cancelAllTabClosure(TabModelSelectorTabModelObserver modelSelectorModelObserver) { - List tabs = mModel.getAllTabs(); - for (int i = 0; i < tabs.size(); i++) { - modelSelectorModelObserver.tabClosureUndone(tabs.get(i)); + private void cancelTabsClosure( + TabModelSelectorTabModelObserver modelSelectorModelObserver, List tabsToUndo) { + for (int i = 0; i < tabsToUndo.size(); i++) { + modelSelectorModelObserver.tabClosureUndone(tabsToUndo.get(i)); } } diff --git a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java index 40096dc4a8c1e6..2e237cbb775011 100644 --- a/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java +++ b/chrome/browser/tab_group/java/src/org/chromium/chrome/browser/tasks/tab_groups/TabGroupModelFilter.java @@ -527,6 +527,17 @@ public List getRelatedTabList(int id) { return getRelatedTabList(group.getTabIdList()); } + @Override + public List getRelatedTabIds(int tabId) { + Tab tab = TabModelUtils.getTabById(getTabModel(), tabId); + if (tab == null) return super.getRelatedTabIds(tabId); + + int groupId = getRootId(tab); + TabGroup group = mGroupIdToGroupMap.get(groupId); + if (group == null) return super.getRelatedTabIds(TabModel.INVALID_TAB_INDEX); + return Collections.unmodifiableList(group.getTabIdList()); + } + /** * This method returns all tabs in a tab group with reference to {@code tabRootId} as group id. * diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java index 2207d93498ce7d..3f1f4aebf877f7 100644 --- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java +++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelFilter.java @@ -27,6 +27,8 @@ public abstract class TabModelFilter implements TabModelObserver, TabList { private static final List sEmptyRelatedTabList = Collections.unmodifiableList(new ArrayList()); + private static final List sEmptyRelatedTabIds = + Collections.unmodifiableList(new ArrayList()); private TabModel mTabModel; protected ObserverList mFilteredObservers = new ObserverList<>(); private boolean mTabRestoreCompleted; @@ -100,6 +102,23 @@ public List getRelatedTabList(int tabId) { return Collections.unmodifiableList(relatedTab); } + /** + * Any of the concrete class can override and define a relationship that links a {@link Tab} to + * a list of related {@link Tab}s. By default, this returns an unmodifiable list that only + * contains the given id. Note that the meaning of related can vary + * depending on the filter being applied. + * @param tabId Id of the {@link Tab} try to relate with. + * @return An unmodifiable list of id that relate with the given tab id. + */ + @NonNull + public List getRelatedTabIds(int tabId) { + Tab tab = TabModelUtils.getTabById(getTabModel(), tabId); + if (tab == null) return sEmptyRelatedTabIds; + List relatedTabIds = new ArrayList<>(); + relatedTabIds.add(tabId); + return Collections.unmodifiableList(relatedTabIds); + } + /** * @return An unmodifiable list of {@link Tab}s that are not related to any tabs */ @@ -290,6 +309,13 @@ public void willCloseAllTabs(boolean incognito) { } } + @Override + public void allTabsClosureUndone() { + for (TabModelObserver observer : mFilteredObservers) { + observer.allTabsClosureUndone(); + } + } + @Override public void allTabsClosureCommitted(boolean isIncognito) { for (TabModelObserver observer : mFilteredObservers) { diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java index fb5acd354bde71..0f494c9af800b8 100644 --- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java +++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelObserver.java @@ -119,9 +119,19 @@ default void tabClosureCommitted(Tab tab) {} /** * Called when an "all tabs" closure will happen. + * If multiple tabs are closed, @{@link TabModelObserver#willCloseMultipleTabs(boolean, List)} + * is invoked */ default void willCloseAllTabs(boolean incognito) {} + /** + * Called when multiple tabs closure will happen. If "all tabs" are closed at once, @{@link + * TabModelObserver#willCloseAllTabs(boolean)} is invoked. + * @param allowUndo If undo is allowed on the tab closure. + * @param tabs being closed. + */ + default void willCloseMultipleTabs(boolean allowUndo, List tabs){}; + /** * Called when an "all tabs" closure has been committed and can't be undone anymore. */