[Tab Model] Add DialogType to TabModelActionListener

Add a dialog type so that listeners know which type of dialog was
shown. Use this to determine behaviors as handling of the SYNC vs
COLLABORATION speedbump dialogs may differ.

Bug: 345854441
Change-Id: I13fab14b279059a334b8d786290b25c9a4a0ab9d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5983546
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Calder Kitagawa <ckitagawa@chromium.org>
Reviewed-by: Sky Malice <skym@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1377095}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImpl.java
index 57d76de..9d2de4db 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImpl.java
@@ -15,6 +15,7 @@
 import org.chromium.chrome.browser.data_sharing.DataSharingTabGroupUtils;
 import org.chromium.chrome.browser.data_sharing.DataSharingTabGroupUtils.GroupsPendingDestroy;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.chrome.browser.tabmodel.TabModelRemover.TabModelRemoverFlowHandler;
 import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
@@ -88,6 +89,7 @@
         private final TabClosureParams mOriginalTabClosureParams;
         private @Nullable TabModelActionListener mListener;
         private @Nullable List<Tab> mPlaceholderTabs;
+        private boolean mPreventUndo;
 
         CloseTabsHandler(
                 @NonNull TabGroupModelFilter tabGroupModelFilter,
@@ -113,7 +115,7 @@
 
         @Override
         public void showTabGroupDeletionConfirmationDialog(@NonNull Callback<Integer> onResult) {
-            var adaptedCallback = adaptOnResultCallback(onResult, takeListener());
+            var adaptedCallback = adaptOnResultCallback(onResult, DialogType.SYNC, takeListener());
             if (mOriginalTabClosureParams.isTabGroup) {
                 mActionConfirmationManager.processDeleteGroupAttempt(adaptedCallback);
             } else {
@@ -124,12 +126,14 @@
         @Override
         public void showCollaborationKeepDialog(
                 @MemberRole int memberRole, @NonNull String title, Callback<Integer> onResult) {
+            var adaptedCallback =
+                    adaptOnResultCallback(onResult, DialogType.COLLABORATION, takeListener());
             if (memberRole == MemberRole.OWNER) {
                 mActionConfirmationManager.processCollaborationOwnerRemoveLastTab(
-                        title, adaptOnResultCallback(onResult, takeListener()));
+                        title, adaptedCallback);
             } else if (memberRole == MemberRole.MEMBER) {
                 mActionConfirmationManager.processCollaborationMemberRemoveLastTab(
-                        title, adaptOnResultCallback(onResult, takeListener()));
+                        title, adaptedCallback);
             } else {
                 assert false : "Not reached";
             }
@@ -142,12 +146,14 @@
                     fixupTabClosureParams(
                             mTabGroupModelFilter.getTabModel(),
                             mOriginalTabClosureParams,
-                            mPlaceholderTabs);
+                            mPlaceholderTabs,
+                            mPreventUndo);
             if (newTabClosureParams == null) return;
 
             PassthroughTabRemover.doCloseTabs(mTabGroupModelFilter, newTabClosureParams);
             if (mListener != null) {
-                mListener.onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+                mListener.onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
             }
         }
 
@@ -156,6 +162,26 @@
             mListener = null;
             return listener;
         }
+
+        private @NonNull Callback<Integer> adaptOnResultCallback(
+                @NonNull Callback<Integer> callback,
+                @DialogType int plannedDialogType,
+                @Nullable TabModelActionListener listener) {
+            return (result) -> {
+                boolean isImmediateContinue = result == ActionConfirmationResult.IMMEDIATE_CONTINUE;
+                // Sync dialogs interrupt the flow and as such undo operations after the dialog is
+                // shown should be suppressed as the user already had an opportunity to abort.
+                if (plannedDialogType == DialogType.SYNC) {
+                    mPreventUndo = !isImmediateContinue;
+                }
+                callback.onResult(result);
+                if (listener != null) {
+                    @DialogType
+                    int dialogType = isImmediateContinue ? DialogType.NONE : plannedDialogType;
+                    listener.onConfirmationDialogResult(dialogType, result);
+                }
+            };
+        }
     }
 
     private static class RemoveTabHandler implements TabModelRemoverFlowHandler {
@@ -209,24 +235,25 @@
             }
             PassthroughTabRemover.doRemoveTab(tabModel, mTabToRemove);
             if (mListener != null) {
-                mListener.onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+                mListener.onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
             }
         }
     }
 
-    // TODO(crbug.com/345854441): Consider overriding `allowUndo` in the event placeholder tabs were
-    // not created and a dialog was shown to align with existing behaviors.
     @VisibleForTesting
     protected static @Nullable TabClosureParams fixupTabClosureParams(
             @NonNull TabModel tabModel,
             @NonNull TabClosureParams params,
-            @Nullable List<Tab> placeholderTabs) {
+            @Nullable List<Tab> placeholderTabs,
+            boolean preventUndo) {
         boolean createdPlaceholders = placeholderTabs != null && !placeholderTabs.isEmpty();
 
         boolean isAllTabs = params.tabCloseType == TabCloseType.ALL;
         // If we did not create placeholder tabs and are closing all tabs it is safe to just
         // proceed. There are protections to prevent double closure already in place in TabModel
-        // for the all tabs case.
+        // for the all tabs case. It is also safe to ignore `preventUndo` as these operations should
+        // always have an undo option.
         if (!createdPlaceholders && isAllTabs) {
             return params;
         }
@@ -274,25 +301,15 @@
             return TabClosureParams.closeTab(tabsToClose.get(0))
                     .recommendedNextTab(params.recommendedNextTab)
                     .uponExit(params.uponExit && !createdPlaceholders)
-                    .allowUndo(params.allowUndo)
+                    .allowUndo(params.allowUndo && !preventUndo)
                     .withUndoRunnable(undoRunnable)
                     .build();
         }
         return TabClosureParams.closeTabs(tabsToClose)
-                .allowUndo(params.allowUndo)
+                .allowUndo(params.allowUndo && !preventUndo)
                 .hideTabGroups(params.hideTabGroups)
                 .saveToTabRestoreService(params.saveToTabRestoreService)
                 .withUndoRunnable(undoRunnable)
                 .build();
     }
-
-    private static @NonNull Callback<Integer> adaptOnResultCallback(
-            @NonNull Callback<Integer> callback, @Nullable TabModelActionListener listener) {
-        return (result) -> {
-            callback.onResult(result);
-            if (listener != null) {
-                listener.onConfirmationDialogResult(result);
-            }
-        };
-    }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImplUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImplUnitTest.java
index 93aa7ff..51b4ecf 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImplUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabRemoverImplUnitTest.java
@@ -34,6 +34,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
+import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.chrome.browser.tabmodel.TabModelRemover.TabModelRemoverFlowHandler;
 import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModel;
@@ -115,7 +116,9 @@
 
         handler.performAction();
         verify(mTabGroupModelFilter).closeTabs(eq(params));
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verifyNoMoreInteractions(mListener);
     }
 
@@ -151,7 +154,9 @@
         handler.showTabGroupDeletionConfirmationDialog(mOnResult);
         verify(mActionConfirmationManager).processDeleteGroupAttempt(mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verify(mOnResult).onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
 
         handler.performAction();
@@ -188,9 +193,11 @@
 
         handler.showTabGroupDeletionConfirmationDialog(mOnResult);
         verify(mActionConfirmationManager).processCloseTabAttempt(mOnResultCaptor.capture());
-        mOnResultCaptor.getValue().onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
-        verify(mOnResult).onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.SYNC, ActionConfirmationResult.CONFIRMATION_POSITIVE);
+        verify(mOnResult).onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
 
         handler.performAction();
         verify(mTabGroupModelFilter).closeTabs(any(TabClosureParams.class));
@@ -231,7 +238,8 @@
                 .processCollaborationOwnerRemoveLastTab(eq(TITLE), mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
         verify(mListener)
-                .onConfirmationDialogResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
+                .onConfirmationDialogResult(
+                        DialogType.COLLABORATION, ActionConfirmationResult.CONFIRMATION_POSITIVE);
         verify(mOnResult).onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
 
         handler.performAction();
@@ -273,7 +281,8 @@
                 .processCollaborationMemberRemoveLastTab(eq(TITLE), mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
         verify(mListener)
-                .onConfirmationDialogResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
+                .onConfirmationDialogResult(
+                        DialogType.COLLABORATION, ActionConfirmationResult.CONFIRMATION_NEGATIVE);
         verify(mOnResult).onResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
 
         handler.performAction();
@@ -287,7 +296,7 @@
         TabClosureParams params = TabClosureParams.closeAllTabs().build();
         TabClosureParams newParams =
                 TabRemoverImpl.fixupTabClosureParams(
-                        mTabModel, params, /* placeholderTabs= */ null);
+                        mTabModel, params, /* placeholderTabs= */ null, /* preventUndo= */ false);
         assertEquals(params, newParams);
     }
 
@@ -297,7 +306,8 @@
         Tab tab1 = mTabModel.addTab(/* id= */ 1);
         TabClosureParams params = TabClosureParams.closeAllTabs().build();
         TabClosureParams newParams =
-                TabRemoverImpl.fixupTabClosureParams(mTabModel, params, List.of(tab1));
+                TabRemoverImpl.fixupTabClosureParams(
+                        mTabModel, params, List.of(tab1), /* preventUndo= */ false);
         assertNotEquals(params, newParams);
         assertFalse(newParams.isAllTabs);
         assertEquals(List.of(tab0), newParams.tabs);
@@ -313,11 +323,26 @@
                         .build();
         TabClosureParams newParams =
                 TabRemoverImpl.fixupTabClosureParams(
-                        mTabModel, params, /* placeholderTabs= */ null);
+                        mTabModel, params, /* placeholderTabs= */ null, /* preventUndo= */ false);
         assertEquals(params, newParams);
     }
 
     @Test
+    public void testUpdateTabClosureParams_NoPlaceholders_CloseTab_PreventUndo() {
+        Tab tab0 = mTabModel.addTab(/* id= */ 0);
+        TabClosureParams params =
+                TabClosureParams.closeTab(tab0)
+                        .allowUndo(true)
+                        .withUndoRunnable(mUndoRunnable)
+                        .build();
+        TabClosureParams newParams =
+                TabRemoverImpl.fixupTabClosureParams(
+                        mTabModel, params, /* placeholderTabs= */ null, /* preventUndo= */ true);
+        assertEquals(params.tabs, newParams.tabs);
+        assertFalse(newParams.allowUndo);
+    }
+
+    @Test
     public void testUpdateTabClosureParams_Placeholder_CloseTab() {
         Tab tab0 = mTabModel.addTab(/* id= */ 0);
         Tab tab1 = mTabModel.addTab(/* id= */ 1);
@@ -328,7 +353,8 @@
                         .build();
         List<Tab> placeholderTabs = List.of(tab1);
         TabClosureParams newParams =
-                TabRemoverImpl.fixupTabClosureParams(mTabModel, params, placeholderTabs);
+                TabRemoverImpl.fixupTabClosureParams(
+                        mTabModel, params, placeholderTabs, /* preventUndo= */ false);
         assertNotEquals(params, newParams);
         assertEquals(params.tabCloseType, newParams.tabCloseType);
         assertEquals(params.tabs, newParams.tabs);
@@ -356,11 +382,28 @@
                         .build();
         TabClosureParams newParams =
                 TabRemoverImpl.fixupTabClosureParams(
-                        mTabModel, params, /* placeholderTabs= */ null);
+                        mTabModel, params, /* placeholderTabs= */ null, /* preventUndo= */ false);
         assertEquals(params, newParams);
     }
 
     @Test
+    public void testUpdateTabClosureParams_NoPlaceholders_CloseTabs_PreventUndo() {
+        mTabModel.addTab(/* id= */ 0);
+        Tab tab1 = mTabModel.addTab(/* id= */ 1);
+        TabClosureParams params =
+                TabClosureParams.closeTabs(List.of(tab1))
+                        .allowUndo(true)
+                        .hideTabGroups(true)
+                        .withUndoRunnable(mUndoRunnable)
+                        .build();
+        TabClosureParams newParams =
+                TabRemoverImpl.fixupTabClosureParams(
+                        mTabModel, params, /* placeholderTabs= */ null, /* preventUndo= */ true);
+        assertEquals(params.tabs, newParams.tabs);
+        assertFalse(newParams.allowUndo);
+    }
+
+    @Test
     public void testUpdateTabClosureParams_Placeholder_CloseTabs() {
         Tab tab0 = mTabModel.addTab(/* id= */ 0);
         Tab tab1 = mTabModel.addTab(/* id= */ 1);
@@ -372,7 +415,8 @@
                         .build();
         List<Tab> placeholderTabs = List.of(tab2);
         TabClosureParams newParams =
-                TabRemoverImpl.fixupTabClosureParams(mTabModel, params, placeholderTabs);
+                TabRemoverImpl.fixupTabClosureParams(
+                        mTabModel, params, placeholderTabs, /* preventUndo= */ false);
         assertNotEquals(params, newParams);
         assertEquals(params.tabCloseType, newParams.tabCloseType);
         assertEquals(params.tabs, newParams.tabs);
@@ -417,7 +461,9 @@
 
         handler.performAction();
         verify(mTabModel).removeTab(tab0);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verifyNoMoreInteractions(mListener);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImpl.java
index 356ec7e..60025bae 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImpl.java
@@ -16,6 +16,7 @@
 import org.chromium.chrome.browser.data_sharing.DataSharingTabGroupUtils;
 import org.chromium.chrome.browser.data_sharing.DataSharingTabGroupUtils.GroupsPendingDestroy;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.chrome.browser.tabmodel.TabModelRemover.TabModelRemoverFlowHandler;
 import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
@@ -140,7 +141,7 @@
 
         @Override
         public void showTabGroupDeletionConfirmationDialog(@NonNull Callback<Integer> onResult) {
-            var adaptedCallback = adaptOnResultCallback(onResult, takeListener());
+            var adaptedCallback = adaptOnResultCallback(onResult, DialogType.SYNC, takeListener());
             if (mIsTabGroup) {
                 mActionConfirmationManager.processUngroupAttempt(adaptedCallback);
             } else {
@@ -151,12 +152,14 @@
         @Override
         public void showCollaborationKeepDialog(
                 @MemberRole int memberRole, @NonNull String title, Callback<Integer> onResult) {
+            var adaptedCallback =
+                    adaptOnResultCallback(onResult, DialogType.COLLABORATION, takeListener());
             if (memberRole == MemberRole.OWNER) {
                 mActionConfirmationManager.processCollaborationOwnerRemoveLastTab(
-                        title, adaptOnResultCallback(onResult, takeListener()));
+                        title, adaptedCallback);
             } else if (memberRole == MemberRole.MEMBER) {
                 mActionConfirmationManager.processCollaborationMemberRemoveLastTab(
-                        title, adaptOnResultCallback(onResult, takeListener()));
+                        title, adaptedCallback);
             } else {
                 assert false : "Not reached";
             }
@@ -175,7 +178,8 @@
 
             PassthroughTabUngrouper.doUngroupTabs(filter, newTabsToUngroup, mTrailing);
             if (mListener != null) {
-                mListener.onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+                mListener.onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
             }
         }
 
@@ -187,11 +191,18 @@
     }
 
     private static @NonNull Callback<Integer> adaptOnResultCallback(
-            @NonNull Callback<Integer> callback, @Nullable TabModelActionListener listener) {
+            @NonNull Callback<Integer> callback,
+            @DialogType int plannedDialogType,
+            @Nullable TabModelActionListener listener) {
         return (result) -> {
             callback.onResult(result);
             if (listener != null) {
-                listener.onConfirmationDialogResult(result);
+                @DialogType
+                int dialogType =
+                        result == ActionConfirmationResult.IMMEDIATE_CONTINUE
+                                ? DialogType.NONE
+                                : plannedDialogType;
+                listener.onConfirmationDialogResult(dialogType, result);
             }
         };
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImplUnitTest.java b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImplUnitTest.java
index a280364..35f0789b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImplUnitTest.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tabmodel/TabUngrouperImplUnitTest.java
@@ -29,6 +29,7 @@
 import org.chromium.chrome.browser.profiles.Profile;
 import org.chromium.chrome.browser.tab.Tab;
 import org.chromium.chrome.browser.tab_group_sync.TabGroupSyncServiceFactory;
+import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.chrome.browser.tabmodel.TabModelRemover.TabModelRemoverFlowHandler;
 import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager;
 import org.chromium.chrome.test.util.browser.tabmodel.MockTabModel;
@@ -102,12 +103,14 @@
         handler.performAction();
         verify(mTabGroupModelFilter)
                 .moveTabOutOfGroupInDirection(tab0.getId(), /* trailing= */ true);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verifyNoMoreInteractions(mListener);
     }
 
     @Test
-    public void testUngroupTabsHandler_UngroupTabGroup_RootId_DestructionOnly() {
+    public void testUngroupTabsHandler_UngroupTabGroup_RootId_DestructionOnly_ImmediateContinue() {
         int id = 0;
         Tab tab0 = mTabModel.addTab(id);
         tab0.setTabGroupId(TAB_GROUP_ID.tabGroupId);
@@ -138,7 +141,9 @@
         handler.showTabGroupDeletionConfirmationDialog(mOnResult);
         verify(mActionConfirmationManager).processUngroupAttempt(mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verify(mOnResult).onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
 
         handler.performAction();
@@ -148,6 +153,50 @@
     }
 
     @Test
+    public void
+            testUngroupTabsHandler_UngroupTabGroup_RootId_DestructionOnly_ConfirmationPositive() {
+        int id = 0;
+        Tab tab0 = mTabModel.addTab(id);
+        tab0.setTabGroupId(TAB_GROUP_ID.tabGroupId);
+        tab0.setRootId(id);
+        when(mTabGroupModelFilter.getRelatedTabListForRootId(id)).thenReturn(List.of(tab0));
+        when(mTabGroupModelFilter.isTabInTabGroup(tab0)).thenReturn(true);
+
+        mTabUngrouperImpl.ungroupTabGroup(
+                id, /* trailing= */ true, /* allowDialog= */ true, mListener);
+        verify(mTabModelRemover).doTabRemovalFlow(mHandlerCaptor.capture(), eq(true));
+        TabModelRemoverFlowHandler handler = mHandlerCaptor.getValue();
+
+        SavedTabGroupTab savedTab = new SavedTabGroupTab();
+        savedTab.localId = id;
+        SavedTabGroup savedTabGroup = new SavedTabGroup();
+        savedTabGroup.localId = TAB_GROUP_ID;
+        savedTabGroup.savedTabs.add(savedTab);
+        when(mTabGroupSyncService.getAllGroupIds()).thenReturn(new String[] {SYNC_ID});
+        when(mTabGroupSyncService.getGroup(SYNC_ID)).thenReturn(savedTabGroup);
+
+        GroupsPendingDestroy groupsPendingDestroy = handler.computeGroupsPendingDestroy();
+        assertFalse(groupsPendingDestroy.isEmpty());
+        assertTrue(groupsPendingDestroy.collaborationGroupsDestroyed.isEmpty());
+        assertFalse(groupsPendingDestroy.syncedGroupsDestroyed.isEmpty());
+
+        // No placeholder tabs created.
+
+        handler.showTabGroupDeletionConfirmationDialog(mOnResult);
+        verify(mActionConfirmationManager).processUngroupAttempt(mOnResultCaptor.capture());
+        mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.SYNC, ActionConfirmationResult.CONFIRMATION_POSITIVE);
+        verify(mOnResult).onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
+
+        handler.performAction();
+        verify(mTabGroupModelFilter).moveTabOutOfGroupInDirection(id, /* trailing= */ true);
+
+        verifyNoMoreInteractions(mListener);
+    }
+
+    @Test
     public void testUngroupTabsHandler_UngroupTabGroup_TabGroupId_DestructionOnly() {
         int id = 0;
         Tab tab0 = mTabModel.addTab(id);
@@ -181,7 +230,9 @@
         handler.showTabGroupDeletionConfirmationDialog(mOnResult);
         verify(mActionConfirmationManager).processUngroupAttempt(mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verify(mOnResult).onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
 
         handler.performAction();
@@ -220,7 +271,9 @@
         handler.showTabGroupDeletionConfirmationDialog(mOnResult);
         verify(mActionConfirmationManager).processUngroupTabAttempt(mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
-        verify(mListener).onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+        verify(mListener)
+                .onConfirmationDialogResult(
+                        DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         verify(mOnResult).onResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
 
         handler.performAction();
@@ -263,7 +316,8 @@
                 .processCollaborationOwnerRemoveLastTab(eq(TITLE), mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
         verify(mListener)
-                .onConfirmationDialogResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
+                .onConfirmationDialogResult(
+                        DialogType.COLLABORATION, ActionConfirmationResult.CONFIRMATION_POSITIVE);
         verify(mOnResult).onResult(ActionConfirmationResult.CONFIRMATION_POSITIVE);
 
         handler.performAction();
@@ -306,7 +360,8 @@
                 .processCollaborationMemberRemoveLastTab(eq(TITLE), mOnResultCaptor.capture());
         mOnResultCaptor.getValue().onResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
         verify(mListener)
-                .onConfirmationDialogResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
+                .onConfirmationDialogResult(
+                        DialogType.COLLABORATION, ActionConfirmationResult.CONFIRMATION_NEGATIVE);
         verify(mOnResult).onResult(ActionConfirmationResult.CONFIRMATION_NEGATIVE);
 
         handler.performAction();
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabRemover.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabRemover.java
index 9fd6c94..af5f1d44 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabRemover.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabRemover.java
@@ -10,6 +10,7 @@
 
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
 
 /**
@@ -35,7 +36,8 @@
             @Nullable TabModelActionListener listener) {
         forceCloseTabs(tabClosureParams);
         if (listener != null) {
-            listener.onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+            listener.onConfirmationDialogResult(
+                    DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         }
     }
 
@@ -51,7 +53,8 @@
         assert mTabGroupModelFilterSupplier.hasValue();
         doRemoveTab(mTabGroupModelFilterSupplier.get().getTabModel(), tab);
         if (listener != null) {
-            listener.onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+            listener.onConfirmationDialogResult(
+                    DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         }
     }
 
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabUngrouper.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabUngrouper.java
index b8f16c1..63dc876 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabUngrouper.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/PassthroughTabUngrouper.java
@@ -11,6 +11,7 @@
 import org.chromium.base.Token;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.browser.tab.Tab;
+import org.chromium.chrome.browser.tabmodel.TabModelActionListener.DialogType;
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
 
 import java.util.List;
@@ -77,7 +78,8 @@
 
         doUngroupTabs(filter, tabs, trailing);
         if (listener != null) {
-            listener.onConfirmationDialogResult(ActionConfirmationResult.IMMEDIATE_CONTINUE);
+            listener.onConfirmationDialogResult(
+                    DialogType.NONE, ActionConfirmationResult.IMMEDIATE_CONTINUE);
         }
     }
 
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelActionListener.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelActionListener.java
index ffe82cb..cc9e0f9 100644
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelActionListener.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabModelActionListener.java
@@ -4,19 +4,40 @@
 
 package org.chromium.chrome.browser.tabmodel;
 
+import androidx.annotation.IntDef;
+
 import org.chromium.components.browser_ui.widget.ActionConfirmationResult;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Listener to get updates for actions that may show speedbump dialogs when performing operations on
  * a {@link TabModel}. See {@link TabRemover} and {@link TabUngrouper}.
  */
 public interface TabModelActionListener {
+    /** An enum representing the type of dialog that was shown. */
+    @IntDef({DialogType.NONE, DialogType.SYNC, DialogType.COLLABORATION})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DialogType {
+        /** No dialog was shown. */
+        int NONE = 0;
+
+        /** A dialog about synced group destruction was shown. */
+        int SYNC = 1;
+
+        /** A dialog about collaboration destruction was shown. */
+        int COLLABORATION = 2;
+    }
+
     /**
      * Called with the result of showing the action confirmation dialog for the action. This is
      * guaranteed to be called, and may be called synchronously if no dialog is shown and the action
      * will proceed synchronously. This will be called after the action is triggered.
      *
+     * @param dialogType The type of dialog that was shown.
      * @param result The {@link ActionConfirmationResult}.
      */
-    default void onConfirmationDialogResult(@ActionConfirmationResult int result) {}
+    default void onConfirmationDialogResult(
+            @DialogType int dialogType, @ActionConfirmationResult int result) {}
 }