0

[ SavedTabGroups ] Tab Restore implementation for SavedTabGroups

Initial implementation for restoring SavedTabGroups and their tabs.

List of Changes:
- Ungrouped tabs will restore normally
- Restored tabs which are grouped will be restored normally then saved
- Restored groups will be restored normally and saved
- Restore saved groups will open the saved group and add any tabs that are not in the list of tabs already
- The first index of restored windows will be activated instead of the last index
- The first index of restored groups will be activated instead of the last index

Change-Id: I15c6a2f22b20cd2a01704d31286e253af05de16d
Bug: 335270082
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5460410
Commit-Queue: Darryl James <dljames@chromium.org>
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Eshwar Stalin <estalin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1291461}
This commit is contained in:
dljames
2024-04-23 19:01:47 +00:00
committed by Chromium LUCI CQ
parent 2961448b62
commit ae5419e0b2
12 changed files with 276 additions and 600 deletions

@@ -116,8 +116,7 @@ IN_PROC_BROWSER_TEST_F(BackgroundTabLoadingBrowserTest, MAYBE_RestoreTab) {
Browser* restored_browser = BrowserList::GetInstance()->get(1); Browser* restored_browser = BrowserList::GetInstance()->get(1);
EXPECT_EQ(kDesiredNumberOfTabs, restored_browser->tab_strip_model()->count()); EXPECT_EQ(kDesiredNumberOfTabs, restored_browser->tab_strip_model()->count());
EXPECT_EQ(kDesiredNumberOfTabs - 1, EXPECT_EQ(0, restored_browser->tab_strip_model()->active_index());
restored_browser->tab_strip_model()->active_index());
// All tabs should be loaded by BackgroundTabLoadingPolicy. // All tabs should be loaded by BackgroundTabLoadingPolicy.
for (int i = 0; i < kDesiredNumberOfTabs; i++) { for (int i = 0; i < kDesiredNumberOfTabs; i++) {
@@ -156,8 +155,7 @@ IN_PROC_BROWSER_TEST_F(BackgroundTabLoadingBrowserTest,
Browser* restored_browser = BrowserList::GetInstance()->get(1); Browser* restored_browser = BrowserList::GetInstance()->get(1);
EXPECT_EQ(kDesiredNumberOfTabs, restored_browser->tab_strip_model()->count()); EXPECT_EQ(kDesiredNumberOfTabs, restored_browser->tab_strip_model()->count());
EXPECT_EQ(kDesiredNumberOfTabs - 1, EXPECT_EQ(0, restored_browser->tab_strip_model()->active_index());
restored_browser->tab_strip_model()->active_index());
// These tabs should be loaded by BackgroundTabLoadingPolicy. // These tabs should be loaded by BackgroundTabLoadingPolicy.
EnsureTabFinishedRestoring( EnsureTabFinishedRestoring(

@@ -453,7 +453,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindowAndTab) {
// Restore the first window. The expected_tabstrip_index (second argument) // Restore the first window. The expected_tabstrip_index (second argument)
// indicates the expected active tab. // indicates the expected active tab.
ASSERT_NO_FATAL_FAILURE(RestoreTab(1, starting_tab_count + 1)); ASSERT_NO_FATAL_FAILURE(RestoreTab(1, 0));
Browser* browser = GetBrowser(1); Browser* browser = GetBrowser(1);
EXPECT_EQ(starting_tab_count + 2, browser->tab_strip_model()->count()); EXPECT_EQ(starting_tab_count + 2, browser->tab_strip_model()->count());
@@ -565,24 +565,24 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindowBounds) {
} }
// Close a group not at the end of the current window, then restore it. The // Close a group not at the end of the current window, then restore it. The
// group should be in its original position. // group should be at the end of the tabstrip.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroup) { IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroup) {
AddSomeTabs(browser(), 3); AddSomeTabs(browser(), 3);
tab_groups::TabGroupId group = tab_groups::TabGroupId group =
browser()->tab_strip_model()->AddToNewGroup({1, 2}); browser()->tab_strip_model()->AddToNewGroup({1, 2});
CloseGroup(group); CloseGroup(group);
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 1)); ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 2));
EXPECT_EQ(browser() EXPECT_EQ(browser()
->tab_strip_model() ->tab_strip_model()
->group_model() ->group_model()
->GetTabGroup(group) ->GetTabGroup(group)
->ListTabs(), ->ListTabs(),
gfx::Range(1, 3)); gfx::Range(2, 4));
} }
// Close a grouped tab, then the entire group. Restore both. The group should be // Close a grouped tab, then the entire group. Restore both. The group should be
// in its original position, with the tab in its original position as well. // opened at the end of the tabstrip.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTabThenGroup) { IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTabThenGroup) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups()); ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
AddSomeTabs(browser(), 3); AddSomeTabs(browser(), 3);
@@ -591,7 +591,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTabThenGroup) {
CloseTab(1); CloseTab(1);
CloseGroup(group); CloseGroup(group);
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 0)); ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 1));
ASSERT_NO_FATAL_FAILURE(RestoreTab(0, 1)); ASSERT_NO_FATAL_FAILURE(RestoreTab(0, 1));
EXPECT_EQ(browser() EXPECT_EQ(browser()
@@ -599,7 +599,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTabThenGroup) {
->group_model() ->group_model()
->GetTabGroup(group) ->GetTabGroup(group)
->ListTabs(), ->ListTabs(),
gfx::Range(0, 3)); gfx::Range(1, 4));
} }
// Close a group that contains all tabs in a window, resulting in the window // Close a group that contains all tabs in a window, resulting in the window
@@ -618,9 +618,11 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupInNewWindow) {
ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER); ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
ASSERT_EQ(2u, active_browser_list_->size()); ASSERT_EQ(2u, active_browser_list_->size());
// Close the original group, which closes the original window. // Close the original group, which closes the original window. We spawn a new
CloseGroup(group); // tab if the group is the only element in the browser and is closing. This
ui_test_utils::WaitForBrowserToClose(); // prevents the browser from actually closing, so we close it manually
// instead.
CloseBrowserSynchronously(browser());
EXPECT_EQ(1u, active_browser_list_->size()); EXPECT_EQ(1u, active_browser_list_->size());
// Restore the original group, which should create a new window. // Restore the original group, which should create a new window.
@@ -732,7 +734,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupWithUnloadHandlerRejected) {
->group_model() ->group_model()
->GetTabGroup(group) ->GetTabGroup(group)
->ListTabs(), ->ListTabs(),
gfx::Range(1, 3)); gfx::Range(2, 4));
EXPECT_EQ(browser()->tab_strip_model()->count(), 4); EXPECT_EQ(browser()->tab_strip_model()->count(), 4);
// Close the tab with the unload handler, otherwise it will prevent test // Close the tab with the unload handler, otherwise it will prevent test
@@ -1007,7 +1009,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindow) {
EXPECT_EQ(initial_tab_count + 2, browser->tab_strip_model()->count()); EXPECT_EQ(initial_tab_count + 2, browser->tab_strip_model()->count());
EXPECT_TRUE(content::WaitForLoadStop(tab_added_waiter.Wait())); EXPECT_TRUE(content::WaitForLoadStop(tab_added_waiter.Wait()));
EXPECT_EQ(initial_tab_count + 1, browser->tab_strip_model()->active_index()); EXPECT_EQ(0, browser->tab_strip_model()->active_index());
content::WebContents* restored_tab = content::WebContents* restored_tab =
browser->tab_strip_model()->GetWebContentsAt(initial_tab_count + 1); browser->tab_strip_model()->GetWebContentsAt(initial_tab_count + 1);
EnsureTabFinishedRestoring(restored_tab); EnsureTabFinishedRestoring(restored_tab);
@@ -1178,18 +1180,18 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest,
browser2 = GetBrowser(1); browser2 = GetBrowser(1);
EXPECT_EQ(tabs_count, browser2->tab_strip_model()->count()); EXPECT_EQ(tabs_count, browser2->tab_strip_model()->count());
EXPECT_EQ(tabs_count - 1, browser2->tab_strip_model()->active_index()); EXPECT_EQ(0, browser2->tab_strip_model()->active_index());
// These two tabs should be loaded by TabLoader. // These two tabs should be loaded by TabLoader.
EnsureTabFinishedRestoring(
browser2->tab_strip_model()->GetWebContentsAt(tabs_count - 1));
EnsureTabFinishedRestoring(browser2->tab_strip_model()->GetWebContentsAt(0)); EnsureTabFinishedRestoring(browser2->tab_strip_model()->GetWebContentsAt(0));
EnsureTabFinishedRestoring(browser2->tab_strip_model()->GetWebContentsAt(1));
// The following isn't necessary but just to be sure there is no any async // The following isn't necessary but just to be sure there is no any async
// task that could have an impact on the expectations below. // task that could have an impact on the expectations below.
content::RunAllPendingInMessageLoop(); content::RunAllPendingInMessageLoop();
// These tabs shouldn't want to be loaded. // These tabs shouldn't want to be loaded.
for (int tab_idx = 1; tab_idx < tabs_count - 1; ++tab_idx) { for (int tab_idx = 2; tab_idx < tabs_count; ++tab_idx) {
auto* contents = browser2->tab_strip_model()->GetWebContentsAt(tab_idx); auto* contents = browser2->tab_strip_model()->GetWebContentsAt(tab_idx);
EXPECT_FALSE(contents->IsLoading()); EXPECT_FALSE(contents->IsLoading());
EXPECT_TRUE(contents->GetController().NeedsReload()); EXPECT_TRUE(contents->GetController().NeedsReload());
@@ -1261,7 +1263,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindowWithName) {
EXPECT_EQ(1u, active_browser_list_->size()); EXPECT_EQ(1u, active_browser_list_->size());
// Restore the first browser. // Restore the first browser.
ASSERT_NO_FATAL_FAILURE(RestoreTab(1, 1)); ASSERT_NO_FATAL_FAILURE(RestoreTab(1, 0));
Browser* browser = GetBrowser(1); Browser* browser = GetBrowser(1);
EXPECT_EQ("foobar", browser->user_title()); EXPECT_EQ("foobar", browser->user_title());
} }
@@ -1301,8 +1303,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreSingleGroupedTab) {
} }
// Closing the last tab in a collapsed group then restoring will place the group // Closing the last tab in a collapsed group then restoring will place the group
// back expanded with its metadata. // back collapsed with its metadata.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreCollapsedGroupTab_ExpandsGroup) { IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreCollapsedGroupTab) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups()); ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
const int tab_count = AddSomeTabs(browser(), 1); const int tab_count = AddSomeTabs(browser(), 1);
@@ -1336,13 +1338,12 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreCollapsedGroupTab_ExpandsGroup) {
ASSERT_TRUE(data); ASSERT_TRUE(data);
EXPECT_EQ(data->title(), visual_data.title()); EXPECT_EQ(data->title(), visual_data.title());
EXPECT_EQ(data->color(), visual_data.color()); EXPECT_EQ(data->color(), visual_data.color());
EXPECT_FALSE(data->is_collapsed()); EXPECT_TRUE(data->is_collapsed());
} }
// Closing a tab in a collapsed group then restoring the tab will expand the // Closing a tab in a collapsed group then restoring the tab will not expand the
// group upon restore. // group upon restore.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoCollapsedGroup) {
RestoreTabIntoCollapsedGroup_ExpandsGroup) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups()); ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
const int tab_count = AddSomeTabs(browser(), 2); const int tab_count = AddSomeTabs(browser(), 2);
@@ -1374,11 +1375,11 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest,
->visual_data(); ->visual_data();
EXPECT_EQ(data->title(), visual_data.title()); EXPECT_EQ(data->title(), visual_data.title());
EXPECT_EQ(data->color(), visual_data.color()); EXPECT_EQ(data->color(), visual_data.color());
EXPECT_FALSE(data->is_collapsed()); EXPECT_TRUE(data->is_collapsed());
} }
// Closing a tab in a group then updating the metadata before restoring will // Closing a tab in a group then updating the metadata before restoring will
// place the group back without updating the metadata. // place the group back and update the metadata.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoGroup) { IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoGroup) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups()); ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
@@ -1408,8 +1409,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoGroup) {
->GetTabGroupForTab(closed_tab_index) ->GetTabGroupForTab(closed_tab_index)
.value()); .value());
const tab_groups::TabGroupVisualData* data = group->visual_data(); const tab_groups::TabGroupVisualData* data = group->visual_data();
EXPECT_EQ(data->title(), visual_data_2.title()); EXPECT_EQ(data->title(), visual_data_1.title());
EXPECT_EQ(data->color(), visual_data_2.color()); EXPECT_EQ(data->color(), visual_data_1.color());
} }
// Closing a tab in a group then moving the group to a new window before // Closing a tab in a group then moving the group to a new window before
@@ -1721,7 +1722,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest,
// Restore closed group. This should record kTimeBetweenGroupClosedAndRestored // Restore closed group. This should record kTimeBetweenGroupClosedAndRestored
// histogram. // histogram.
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 1)); ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 2));
EXPECT_EQ( EXPECT_EQ(
histogram_tester.GetAllSamples(kTimeBetweenGroupClosedAndRestored).size(), histogram_tester.GetAllSamples(kTimeBetweenGroupClosedAndRestored).size(),
@@ -1888,7 +1889,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoredWindowHasNewGroupIds) {
ASSERT_EQ(3, third_browser->tab_strip_model()->count()); ASSERT_EQ(3, third_browser->tab_strip_model()->count());
// The group ID should be new. // The group ID should be new.
EXPECT_NE(original_group, EXPECT_EQ(original_group,
third_browser->tab_strip_model()->GetTabGroupForTab(1)); third_browser->tab_strip_model()->GetTabGroupForTab(1));
} }
@@ -2109,7 +2110,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest, RestoreSavedGroup) {
ASSERT_TRUE(service->model()->Contains(group)); ASSERT_TRUE(service->model()->Contains(group));
// Close the group. // Close the group.
browser()->tab_strip_model()->CloseAllTabsInGroup(group); // browser()->tab_strip_model()->CloseAllTabsInGroup(group);
CloseGroup(group);
// Restore the group. // Restore the group.
chrome::RestoreTab(browser()); chrome::RestoreTab(browser());
@@ -2136,10 +2138,10 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest, RestoreSavedGroup) {
gfx::Range(1, 3)); gfx::Range(1, 3));
} }
// Close a saved group, then restore it. The group should continue to be saved // Close a saved group and restore it. The group should not have updated its
// and update its color to match the restored entry. // color to match the restored entry.
IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest, IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
RestoreSavedGroupUpdatesColor) { RestoreSavedGroupDoesNotUpdateVisualData) {
AddTabs(browser(), 2); AddTabs(browser(), 2);
tab_groups::SavedTabGroupKeyedService* service = tab_groups::SavedTabGroupKeyedService* service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile( tab_groups::SavedTabGroupServiceFactory::GetForProfile(
@@ -2165,20 +2167,23 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
// Close the group. // Close the group.
browser()->tab_strip_model()->CloseAllTabsInGroup(group); browser()->tab_strip_model()->CloseAllTabsInGroup(group);
// Update the color of the saved group. // Update the visual data of the saved group.
tab_groups::TabGroupVisualData new_visual_data( std::u16string new_title = u"This is a new title";
u"This is a new title", tab_groups::TabGroupColorId::kCyan); tab_groups::TabGroupColorId new_color = tab_groups::TabGroupColorId::kCyan;
tab_groups::TabGroupVisualData new_visual_data(new_title, new_color);
service->model()->UpdateVisualData(saved_group_id, &new_visual_data); service->model()->UpdateVisualData(saved_group_id, &new_visual_data);
// Restore it. // Restore it.
chrome::RestoreTab(browser()); chrome::RestoreTab(browser());
// Check that the restored group has the old group color. // Check that the restored group keeps the new visual data.
const tab_groups::SavedTabGroup* const saved_group = const tab_groups::SavedTabGroup* const saved_group =
service->model()->Get(saved_group_id); service->model()->Get(saved_group_id);
CHECK(saved_group); CHECK(saved_group);
EXPECT_EQ(original_title, saved_group->title()); EXPECT_NE(original_title, saved_group->title());
EXPECT_EQ(original_color, saved_group->color()); EXPECT_NE(original_color, saved_group->color());
EXPECT_EQ(new_title, saved_group->title());
EXPECT_EQ(new_color, saved_group->color());
} }
// Verify that any tabs that exist in the restored group but not the saved group // Verify that any tabs that exist in the restored group but not the saved group
@@ -2443,6 +2448,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
chrome::RestoreTab(browser()); chrome::RestoreTab(browser());
saved_group = *service->model()->Get(saved_group_id); saved_group = *service->model()->Get(saved_group_id);
ASSERT_EQ(
1u, browser()->tab_strip_model()->group_model()->ListTabGroups().size());
// Verify that the second tab was added to the group. // Verify that the second tab was added to the group.
EXPECT_EQ(2u, saved_group.saved_tabs().size()); EXPECT_EQ(2u, saved_group.saved_tabs().size());
@@ -2666,15 +2673,15 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
const std::vector<tab_groups::TabGroupId>& second_browser_group_ids = const std::vector<tab_groups::TabGroupId>& second_browser_group_ids =
second_browser->tab_strip_model()->group_model()->ListTabGroups(); second_browser->tab_strip_model()->group_model()->ListTabGroups();
// Verify there is only 1 saved group, the first browser has 2 tabs (tabs no // Verify there is only 1 saved group, the first browser has 4 tabs (how it
// in the group), and the second browser has 3 tabs (1 tab group). // was originally), and the second browser has 1 tab (new tab page).
EXPECT_EQ(1, service->model()->Count()); EXPECT_EQ(1, service->model()->Count());
EXPECT_EQ(2, first_browser->tab_strip_model()->count()); EXPECT_EQ(4, first_browser->tab_strip_model()->count());
EXPECT_EQ(3, second_browser->tab_strip_model()->count()); EXPECT_EQ(1, second_browser->tab_strip_model()->count());
EXPECT_TRUE(first_browser_group_ids.empty()); EXPECT_TRUE(second_browser_group_ids.empty());
EXPECT_EQ(1u, second_browser_group_ids.size()); EXPECT_EQ(1u, first_browser_group_ids.size());
EXPECT_TRUE(service->model()->Contains(second_browser_group_ids[0])); EXPECT_TRUE(service->model()->Contains(first_browser_group_ids[0]));
EXPECT_TRUE(service->model()->Contains(saved_group_id)); EXPECT_TRUE(service->model()->Contains(saved_group_id));
tab_groups::SavedTabGroup saved_group = tab_groups::SavedTabGroup saved_group =

@@ -5,8 +5,10 @@
#include "chrome/browser/ui/browser_live_tab_context.h" #include "chrome/browser/ui/browser_live_tab_context.h"
#include <memory> #include <memory>
#include <numeric>
#include <optional> #include <optional>
#include <utility> #include <utility>
#include <vector>
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/metrics/histogram_macros.h" #include "base/metrics/histogram_macros.h"
@@ -17,6 +19,7 @@
#include "chrome/browser/browser_features.h" #include "chrome/browser/browser_features.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_service_utils.h" #include "chrome/browser/sessions/session_service_utils.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_finder.h"
@@ -25,6 +28,7 @@
#include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h" #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_service_factory.h" #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_service_factory.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/tab_group.h" #include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h" #include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -35,11 +39,15 @@
#include "components/saved_tab_groups/saved_tab_group.h" #include "components/saved_tab_groups/saved_tab_group.h"
#include "components/sessions/content/content_live_tab.h" #include "components/sessions/content/content_live_tab.h"
#include "components/sessions/content/content_platform_specific_tab_data.h" #include "components/sessions/content/content_platform_specific_tab_data.h"
#include "components/sessions/core/live_tab_context.h"
#include "components/sessions/core/session_types.h" #include "components/sessions/core/session_types.h"
#include "components/sessions/core/tab_restore_service.h"
#include "components/tab_groups/tab_group_id.h" #include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h" #include "components/tab_groups/tab_group_visual_data.h"
#include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_controller.h"
#include "content/public/browser/session_storage_namespace.h" #include "content/public/browser/session_storage_namespace.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/window_open_disposition.h"
#if BUILDFLAG(ENABLE_SESSION_SERVICE) #if BUILDFLAG(ENABLE_SESSION_SERVICE)
#include "chrome/browser/sessions/tab_loader.h" #include "chrome/browser/sessions/tab_loader.h"
@@ -198,6 +206,11 @@ sessions::LiveTab* BrowserLiveTabContext::AddRestoredTab(
const sessions::tab_restore::Tab& tab, const sessions::tab_restore::Tab& tab,
int tab_index, int tab_index,
bool select) { bool select) {
tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile(
browser_->profile());
CHECK(saved_tab_group_service);
SessionStorageNamespace* storage_namespace = SessionStorageNamespace* storage_namespace =
tab.platform_data tab.platform_data
? static_cast<const sessions::ContentPlatformSpecificTabData*>( ? static_cast<const sessions::ContentPlatformSpecificTabData*>(
@@ -205,26 +218,77 @@ sessions::LiveTab* BrowserLiveTabContext::AddRestoredTab(
->session_storage_namespace() ->session_storage_namespace()
: nullptr; : nullptr;
TabGroupModel* group_model = browser_->tab_strip_model()->group_model(); std::optional<tab_groups::TabGroupId> group_id = tab.group;
const bool first_tab_in_group = std::optional<base::Uuid> saved_group_id = tab.saved_group_id;
group_model && tab.group.has_value() && content::WebContents* web_contents = nullptr;
!group_model->ContainsTabGroup(tab.group.value());
WebContents* web_contents = nullptr; const bool is_normal_tab = !group_id.has_value();
const bool is_grouped_tab_unsaved =
group_id.has_value() && !saved_group_id.has_value();
const bool group_deleted_from_model =
group_id.has_value() && saved_group_id.has_value() &&
!saved_tab_group_service->model()->Contains(saved_group_id.value());
if (is_normal_tab || is_grouped_tab_unsaved || group_deleted_from_model) {
// This is either a normal tab or tab in an unsaved group.
web_contents = chrome::AddRestoredTab(
browser_, tab.navigations, tab_index, tab.normalized_navigation_index(),
tab.extension_app_id, group_id, select, tab.pinned, base::TimeTicks(),
storage_namespace, tab.user_agent_override, tab.extra_data,
/*from_session_restore=*/false);
web_contents = chrome::AddRestoredTab( if (is_grouped_tab_unsaved || group_deleted_from_model) {
browser_, tab.navigations, tab_index, tab.normalized_navigation_index(), browser_->live_tab_context()->SetVisualDataForGroup(
tab.extension_app_id, tab.group, select, tab.pinned, base::TimeTicks(), group_id.value(), tab.group_visual_data.value());
storage_namespace, tab.user_agent_override, tab.extra_data,
false /* from_session_restore */);
// Only update the metadata if the group doesn't already exist since the // Save the group if it was not saved.
// existing group has the latest metadata, which may have changed from the if (!saved_tab_group_service->model()->Contains(group_id.value()) &&
// time the tab was closed. tab_groups::IsTabGroupsSaveV2Enabled()) {
if (first_tab_in_group) { saved_tab_group_service->SaveGroup(tab.group.value());
const tab_groups::TabGroupVisualData new_data( }
tab.group_visual_data->title(), tab.group_visual_data->color(), false); }
group_model->GetTabGroup(tab.group.value())->SetVisualData(new_data); } else {
CHECK(saved_tab_group_service->model()->Contains(saved_group_id.value()));
auto* saved_group =
saved_tab_group_service->model()->Get(saved_group_id.value());
group_id = saved_group->local_group_id();
if (!group_id.has_value()) {
// Open the group in this browser if it is closed.
group_id = saved_tab_group_service->OpenSavedTabGroupInBrowser(
browser_, saved_group_id.value());
} else {
Browser* source_browser =
tab_groups::SavedTabGroupUtils::GetBrowserWithTabGroupId(
group_id.value());
CHECK(source_browser);
tab_groups::SavedTabGroupUtils::FocusFirstTabOrWindowInOpenGroup(
group_id.value());
if (source_browser != browser_) {
// Move the group to this browser if it was open somewhere else.
tab_groups::SavedTabGroupUtils::MoveGroupToExistingWindow(
source_browser, browser_, group_id.value(), saved_group_id.value());
}
}
std::unordered_set<std::string> saved_urls =
tab_groups::SavedTabGroupUtils::GetURLsInSavedTabGroup(
*saved_tab_group_service, saved_group_id.value());
const sessions::SerializedNavigationEntry& entry =
tab.navigations.at(tab.normalized_navigation_index());
if (!saved_urls.contains(entry.virtual_url().spec())) {
// Restore the tab at the end of the group if we don't have it.
tab_index = browser_->tab_strip_model()->count();
web_contents = chrome::AddRestoredTab(
browser_, tab.navigations, tab_index,
tab.normalized_navigation_index(), tab.extension_app_id, group_id,
select, tab.pinned, base::TimeTicks(), storage_namespace,
tab.user_agent_override, tab.extra_data,
/*from_session_restore=*/false);
} else {
// Do nothing if the tab wasn't added to the group.
return nullptr;
}
} }
#if BUILDFLAG(ENABLE_SESSION_SERVICE) #if BUILDFLAG(ENABLE_SESSION_SERVICE)
@@ -239,11 +303,13 @@ sessions::LiveTab* BrowserLiveTabContext::AddRestoredTab(
DCHECK(web_contents->GetController().NeedsReload()); DCHECK(web_contents->GetController().NeedsReload());
DCHECK(!web_contents->IsLoading()); DCHECK(!web_contents->IsLoading());
} }
std::vector<TabLoader::RestoredTab> restored_tabs; std::vector<TabLoader::RestoredTab> restored_tabs;
restored_tabs.emplace_back(web_contents, is_active, restored_tabs.emplace_back(web_contents, is_active,
!tab.extension_app_id.empty(), tab.pinned, !tab.extension_app_id.empty(), tab.pinned,
tab.group); group_id);
TabLoader::RestoreTabs(restored_tabs, base::TimeTicks::Now()); TabLoader::RestoreTabs(restored_tabs, base::TimeTicks::Now());
#else // BUILDFLAG(ENABLE_SESSION_SERVICE) #else // BUILDFLAG(ENABLE_SESSION_SERVICE)
// Load the tab manually if there is no TabLoader. // Load the tab manually if there is no TabLoader.
web_contents->GetController().LoadIfNecessary(); web_contents->GetController().LoadIfNecessary();

@@ -11,6 +11,7 @@
#include "base/memory/raw_ptr.h" #include "base/memory/raw_ptr.h"
#include "components/sessions/core/live_tab_context.h" #include "components/sessions/core/live_tab_context.h"
#include "components/sessions/core/tab_restore_types.h"
#include "components/tab_groups/tab_group_id.h" #include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h" #include "components/tab_groups/tab_group_visual_data.h"
#include "ui/base/ui_base_types.h" #include "ui/base/ui_base_types.h"

@@ -2,18 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include <memory>
#include <optional>
#include <unordered_set>
#include <vector>
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h" #include "base/memory/raw_ptr.h"
#include "base/metrics/user_metrics.h" #include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h" #include "base/metrics/user_metrics_action.h"
#include "base/supports_user_data.h" #include "base/supports_user_data.h"
#include "base/uuid.h"
#include "chrome/browser/apps/app_service/web_contents_app_id_utils.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/tab_restore_service_factory.h" #include "chrome/browser/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
@@ -21,29 +14,8 @@
#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h" #include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_live_tab_context.h" #include "chrome/browser/ui/browser_live_tab_context.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_service_factory.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/web_applications/web_app_helpers.h"
#include "components/saved_tab_groups/features.h"
#include "components/saved_tab_groups/saved_tab_group.h"
#include "components/saved_tab_groups/saved_tab_group_tab.h"
#include "components/sessions/content/content_serialized_navigation_builder.h"
#include "components/sessions/core/live_tab_context.h"
#include "components/sessions/core/serialized_navigation_entry.h"
#include "components/sessions/core/session_id.h"
#include "components/sessions/core/tab_restore_service.h" #include "components/sessions/core/tab_restore_service.h"
#include "components/sessions/core/tab_restore_service_observer.h" #include "components/sessions/core/tab_restore_service_observer.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "url/gurl.h"
namespace chrome { namespace chrome {
namespace { namespace {
@@ -125,378 +97,6 @@ void BrowserTabRestorer::OnBrowserRemoved(Browser* browser) {
browser_->profile()->SetUserData(kBrowserTabRestorerKey, nullptr); browser_->profile()->SetUserData(kBrowserTabRestorerKey, nullptr);
} }
std::unordered_set<std::string> GetUrlsInSavedTabGroup(
tab_groups::SavedTabGroupKeyedService& saved_tab_group_service,
const base::Uuid& saved_id) {
const tab_groups::SavedTabGroup* const saved_group =
saved_tab_group_service.model()->Get(saved_id);
CHECK(saved_group);
std::unordered_set<std::string> saved_urls;
for (const tab_groups::SavedTabGroupTab& saved_tab :
saved_group->saved_tabs()) {
if (!saved_urls.contains(saved_tab.url().spec())) {
saved_urls.emplace(saved_tab.url().spec());
}
}
return saved_urls;
}
content::WebContents* OpenTabWithNavigationStack(
Browser* browser,
const sessions::tab_restore::Tab& restored_tab) {
const sessions::SerializedNavigationEntry& entry =
restored_tab.navigations.at(restored_tab.normalized_navigation_index());
const GURL tab_url = entry.virtual_url();
content::WebContents* created_contents = created_contents =
tab_groups::SavedTabGroupUtils::OpenTabInBrowser(
tab_url, browser, browser->profile(),
WindowOpenDisposition::NEW_BACKGROUND_TAB);
std::vector<std::unique_ptr<content::NavigationEntry>> entries =
sessions::ContentSerializedNavigationBuilder::ToNavigationEntries(
restored_tab.navigations, browser->profile());
created_contents->GetController().Restore(
restored_tab.normalized_navigation_index(),
content::RestoreType::kRestored, &entries);
CHECK_EQ(0u, entries.size());
if (restored_tab.pinned) {
browser->tab_strip_model()->SetTabPinned(
browser->tab_strip_model()->GetIndexOfWebContents(created_contents),
/*pinned=*/true);
}
return created_contents;
}
// Adds a restored tab to the saved group if its URL does not exist in the
// group.
void AddMissingTabToGroup(
Browser* const browser,
tab_groups::SavedTabGroupKeyedService& saved_tab_group_service,
const base::Uuid& saved_id,
const sessions::tab_restore::Tab& restored_tab,
std::unordered_set<std::string>* const saved_urls) {
const tab_groups::SavedTabGroup* const saved_group =
saved_tab_group_service.model()->Get(saved_id);
CHECK(saved_group);
const sessions::SerializedNavigationEntry& entry =
restored_tab.navigations.at(restored_tab.normalized_navigation_index());
const GURL tab_url = entry.virtual_url();
if (!saved_urls->contains(tab_url.spec())) {
// Restore the tab with is navigation stack.
content::WebContents* created_contents =
OpenTabWithNavigationStack(browser, restored_tab);
// Add the tab to the correct group.
int index =
browser->tab_strip_model()->GetIndexOfWebContents(created_contents);
browser->tab_strip_model()->AddToGroupForRestore(
{index}, saved_group->local_group_id().value());
saved_urls->emplace(tab_url.spec());
}
}
void UpdateGroupVisualData(const tab_groups::TabGroupId& group_id,
const tab_groups::TabGroupVisualData& visual_data) {
TabGroup* const tab_group =
tab_groups::SavedTabGroupUtils::GetTabGroupWithId(group_id);
CHECK(tab_group);
tab_group->SetVisualData(visual_data);
}
void OpenSavedTabGroupAndAddRestoredTabs(
Browser* browser,
const sessions::tab_restore::Group& group,
tab_groups::SavedTabGroupKeyedService& saved_tab_group_service) {
// service, saved id, group
std::optional<tab_groups::TabGroupId> new_group_id =
saved_tab_group_service.OpenSavedTabGroupInBrowser(
browser, group.saved_group_id.value());
CHECK(new_group_id.has_value());
// It could be the case that the current state of the saved group has
// deviated from what is represented in TabRestoreService. Make sure any
// tabs that are not in the saved group are added to it.
std::unordered_set<std::string> urls_in_saved_group = GetUrlsInSavedTabGroup(
saved_tab_group_service, group.saved_group_id.value());
for (const std::unique_ptr<sessions::tab_restore::Tab>& grouped_tab :
group.tabs) {
AddMissingTabToGroup(browser, saved_tab_group_service,
group.saved_group_id.value(), *grouped_tab.get(),
&urls_in_saved_group);
}
UpdateGroupVisualData(new_group_id.value(), group.visual_data);
}
void OpenTabGroup(sessions::TabRestoreService& tab_restore_service,
const sessions::tab_restore::Group& group,
Browser* browser) {
tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile(
browser->profile());
CHECK(saved_tab_group_service);
const std::optional<base::Uuid>& saved_id = group.saved_group_id;
const bool is_group_saved =
saved_id.has_value() &&
saved_tab_group_service->model()->Contains(saved_id.value());
if (!is_group_saved) {
// Copy these values so they are not overwritten when we remove the entry
// from TabRestoreService .
tab_groups::TabGroupId group_id = group.group_id;
tab_groups::TabGroupVisualData visual_data = group.visual_data;
// If the group is not saved, restore it normally, and save it.
tab_restore_service.RestoreMostRecentEntry(browser->live_tab_context());
saved_tab_group_service->SaveGroup(group_id);
// Update the color and title of the group appropriately.
UpdateGroupVisualData(group_id, visual_data);
return;
}
const SessionID& session_id = group.id;
OpenSavedTabGroupAndAddRestoredTabs(browser, group, *saved_tab_group_service);
// Clean up TabRestoreService.
tab_restore_service.RemoveEntryById(session_id);
}
void OpenTab(sessions::TabRestoreService& tab_restore_service,
const sessions::tab_restore::Tab& tab,
Browser* browser) {
tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile(
browser->profile());
CHECK(saved_tab_group_service);
// This value is copied here since it is used throughout this function.
std::optional<tab_groups::TabGroupId> group_id = tab.group;
const bool is_group_saved =
group_id.has_value() && tab.saved_group_id.has_value() &&
saved_tab_group_service->model()->Contains(tab.saved_group_id.value());
if (!is_group_saved) {
// Copy these values so they are not overwritten when we make calls to the
// TabRestoreService that will update its list of entries.
std::optional<base::Uuid> saved_id = tab.saved_group_id;
std::optional<tab_groups::TabGroupVisualData> visual_data =
tab.group_visual_data;
// If the tab is not in a group or has not been saved restore it normally.
tab_restore_service.RestoreMostRecentEntry(browser->live_tab_context());
if (group_id.has_value() &&
!saved_tab_group_service->model()->Contains(group_id.value())) {
// Save the group if it isn't already saved.
saved_tab_group_service->SaveGroup(group_id.value());
}
// Update the color and title of the group appropriately.
if (visual_data.has_value()) {
UpdateGroupVisualData(group_id.value(), visual_data.value());
}
return;
}
const SessionID& session_id = tab.id;
const std::optional<base::Uuid>& saved_id = tab.saved_group_id;
const std::optional<tab_groups::TabGroupVisualData>& visual_data =
tab.group_visual_data;
const tab_groups::SavedTabGroup* const saved_group =
saved_tab_group_service->model()->Get(saved_id.value());
if (saved_group->local_group_id().has_value()) {
// If the group is open already, restore the tab normally.
tab_restore_service.RestoreMostRecentEntry(browser->live_tab_context());
// Move the tab into the correct group. This happens in cases where the
// original group id was regenerated (such as when calling
// SavedTabGroupKeyedService::OpenSavedTabGroupInBrowser).
int index = browser->tab_strip_model()->active_index();
browser->tab_strip_model()->AddToExistingGroup(
{index}, saved_group->local_group_id().value(), /*add_to_end=*/true);
return;
}
std::optional<tab_groups::TabGroupId> new_group_id =
saved_tab_group_service->OpenSavedTabGroupInBrowser(browser,
saved_id.value());
CHECK(new_group_id.has_value());
// It could be the case that the current state of the saved group has deviated
// from what is represented in TabRestoreService. Make sure any tabs that are
// not in the saved group are added to it.
std::unordered_set<std::string> urls_in_saved_group =
GetUrlsInSavedTabGroup(*saved_tab_group_service, saved_id.value());
AddMissingTabToGroup(browser, *saved_tab_group_service, saved_id.value(), tab,
&urls_in_saved_group);
if (visual_data.has_value()) {
UpdateGroupVisualData(new_group_id.value(), visual_data.value());
}
// Clean up TabRestoreService.
tab_restore_service.RemoveEntryById(session_id);
}
// |app_name| can could be for an app that has been uninstalled. In that
// case we don't want to open an app window. Note that |app_name| is also used
// for other types of windows like dev tools and we always want to open an
// app window in those cases.
bool ShouldCreateAppWindowForAppName(Profile* profile,
const std::string& app_name) {
if (app_name.empty()) {
return false;
}
// Only need to check that the app is installed if |app_name| is for a
// platform app or web app. (|app_name| could also be for a devtools window.)
const std::string app_id = web_app::GetAppIdFromApplicationName(app_name);
if (app_id.empty()) {
return true;
}
return apps::IsInstalledApp(profile, app_id);
}
Browser* CreateBrowserWindow(Profile* profile,
const sessions::tab_restore::Window& window) {
std::unique_ptr<Browser::CreateParams> create_params;
if (ShouldCreateAppWindowForAppName(profile, window.app_name)) {
// Only trusted app popup windows should ever be restored.
if (window.type == sessions::SessionWindow::TYPE_APP_POPUP) {
create_params = std::make_unique<Browser::CreateParams>(
Browser::CreateParams::CreateForAppPopup(
window.app_name, /*trusted_source=*/true, window.bounds, profile,
/*user_gesture=*/true));
} else {
create_params = std::make_unique<Browser::CreateParams>(
Browser::CreateParams::CreateForApp(
window.app_name, /*trusted_source=*/true, window.bounds, profile,
/*user_gesture=*/true));
}
} else {
create_params = std::make_unique<Browser::CreateParams>(
Browser::CreateParams(profile, true));
create_params->initial_bounds = window.bounds;
}
create_params->initial_show_state = window.show_state;
create_params->initial_workspace = window.workspace;
create_params->user_title = window.user_title;
return Browser::Create(*create_params.get());
}
void RecreateAndSaveTabGroup(
Browser* browser,
const sessions::tab_restore::Group& group,
tab_groups::SavedTabGroupKeyedService& saved_tab_group_service) {
// If the group is not saved:
// 0. Generate a new tab group id to avoid conflicts.
// 1. Open all of the tabs in the new browser window.
// 2. Add all of the tabs to a new tab group using |new_id|.
// 3. Save the group.
tab_groups::TabGroupId new_id = tab_groups::TabGroupId::GenerateNew();
std::vector<int> indices_of_tabs;
for (const std::unique_ptr<sessions::tab_restore::Tab>& tab : group.tabs) {
content::WebContents* opened_tab =
OpenTabWithNavigationStack(browser, *tab.get());
int index = browser->tab_strip_model()->GetIndexOfWebContents(opened_tab);
indices_of_tabs.emplace_back(index);
}
browser->tab_strip_model()->AddToGroupForRestore(indices_of_tabs, new_id);
saved_tab_group_service.SaveGroup(new_id);
UpdateGroupVisualData(new_id, group.visual_data);
}
void OpenWindow(sessions::TabRestoreService& tab_restore_service,
const sessions::tab_restore::Window& window,
Browser* browser) {
tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile(
browser->profile());
CHECK(saved_tab_group_service);
std::unordered_set<std::string> seen_groups;
// This should only be created when we actually need to open a new window.
Browser* new_browser = nullptr;
for (const std::unique_ptr<sessions::tab_restore::Tab>& tab : window.tabs) {
if (!tab->group.has_value()) {
if (!new_browser) {
// Create a new browser window.
new_browser = CreateBrowserWindow(browser->profile(), window);
}
OpenTabWithNavigationStack(new_browser, *tab.get());
continue;
}
// Skip this group if we have already processed it.
if (seen_groups.contains(tab->group.value().ToString())) {
continue;
}
// Process all of the tabs in this group.
seen_groups.emplace(tab->group.value().ToString());
const std::unique_ptr<sessions::tab_restore::Group>& group =
window.groups.at(tab->group.value());
const tab_groups::SavedTabGroup* saved_group =
group->saved_group_id.has_value()
? saved_tab_group_service->model()->Get(
group->saved_group_id.value())
: nullptr;
if ((!saved_group || !saved_group->local_group_id().has_value()) &&
!new_browser) {
// Create a new browser window if we haven't already if:
// 1. The group was not saved and should be opened in the new window.
// 2. The group is not open and should be reopened in the new window.
new_browser = CreateBrowserWindow(browser->profile(), window);
}
if (!saved_group) {
// If the group is not saved:
// 1. Restore normally it
// 2. Save it.
RecreateAndSaveTabGroup(new_browser, *group.get(),
*saved_tab_group_service);
} else {
// If the group is saved:
// 1. Find the browser the group should go in.
// 2. Open the group in the browser.
// 3. Add tabs from restore to the saved group if they do not exist.
Browser* groups_browser =
saved_group->local_group_id().has_value()
? tab_groups::SavedTabGroupUtils::GetBrowserWithTabGroupId(
saved_group->local_group_id().value())
: new_browser;
CHECK(groups_browser);
OpenSavedTabGroupAndAddRestoredTabs(groups_browser, *group.get(),
*saved_tab_group_service);
groups_browser->window()->Show();
}
}
if (new_browser) {
new_browser->window()->Show();
}
tab_restore_service.RemoveEntryById(window.id);
}
} // namespace } // namespace
void RestoreTab(Browser* browser) { void RestoreTab(Browser* browser) {
@@ -509,39 +109,8 @@ void RestoreTab(Browser* browser) {
} }
if (service->IsLoaded()) { if (service->IsLoaded()) {
if (!tab_groups::IsTabGroupsSaveV2Enabled() || service->entries().empty()) { service->RestoreMostRecentEntry(browser->live_tab_context());
// Restore normally. return;
service->RestoreMostRecentEntry(browser->live_tab_context());
return;
}
const std::unique_ptr<sessions::tab_restore::Entry>& most_recent_entry =
service->entries().front();
switch (most_recent_entry->type) {
case sessions::tab_restore::Type::TAB: {
OpenTab(
*service,
static_cast<sessions::tab_restore::Tab&>(*most_recent_entry.get()),
browser);
return;
}
case sessions::tab_restore::Type::WINDOW: {
OpenWindow(*service,
static_cast<sessions::tab_restore::Window&>(
*most_recent_entry.get()),
browser);
return;
}
case sessions::tab_restore::Type::GROUP: {
OpenTabGroup(*service,
static_cast<sessions::tab_restore::Group&>(
*most_recent_entry.get()),
browser);
return;
}
}
NOTREACHED();
} }
BrowserTabRestorer::CreateIfNecessary(browser); BrowserTabRestorer::CreateIfNecessary(browser);

@@ -108,8 +108,8 @@ IN_PROC_BROWSER_TEST_F(BrowserTabRestoreTest, RecentTabsMenuTabDisposition) {
} }
} }
// The middle tab only should have visible disposition. // Only the first tab should have visible disposition.
CheckVisbility(restored_browser->tab_strip_model(), 1); CheckVisbility(restored_browser->tab_strip_model(), 0);
} }
// Expect a selected restored tab to start loading synchronously. // Expect a selected restored tab to start loading synchronously.
@@ -212,6 +212,6 @@ IN_PROC_BROWSER_TEST_F(BrowserTabRestoreTest, DelegateRestoreTabDisposition) {
} }
} }
// The middle tab only should have visible disposition. // Only the first tab should have visible disposition.
CheckVisbility(browser->tab_strip_model(), 1); CheckVisbility(browser->tab_strip_model(), 0);
} }

@@ -124,7 +124,8 @@ SavedTabGroupKeyedService::OpenSavedTabGroupInBrowser(
// Activate the first tab in a group if it is already open. // Activate the first tab in a group if it is already open.
if (saved_group->local_group_id().has_value()) { if (saved_group->local_group_id().has_value()) {
FocusFirstTabOrWindowInOpenGroup(saved_group->local_group_id().value()); tab_groups::SavedTabGroupUtils::FocusFirstTabOrWindowInOpenGroup(
saved_group->local_group_id().value());
return saved_group->local_group_id().value(); return saved_group->local_group_id().value();
} }
@@ -484,38 +485,6 @@ SavedTabGroupKeyedService::GetWebContentsToTabGuidMappingForOpening(
return web_contents; return web_contents;
} }
void SavedTabGroupKeyedService::FocusFirstTabOrWindowInOpenGroup(
tab_groups::TabGroupId local_group_id) {
Browser* browser_for_activation =
SavedTabGroupUtils::GetBrowserWithTabGroupId(local_group_id);
// Only activate the tab group's first tab, if it exists in any browser's
// tabstrip model and it is not in the active tab in the tab group.
CHECK(browser_for_activation);
TabGroup* tab_group =
browser_for_activation->tab_strip_model()->group_model()->GetTabGroup(
local_group_id);
std::optional<int> first_tab = tab_group->GetFirstTab();
std::optional<int> last_tab = tab_group->GetLastTab();
int active_index = browser_for_activation->tab_strip_model()->active_index();
CHECK(first_tab.has_value());
CHECK(last_tab.has_value());
CHECK_GE(active_index, 0);
if (active_index >= first_tab.value() && active_index <= last_tab) {
browser_for_activation->window()->Activate();
return;
}
browser_for_activation->ActivateContents(
browser_for_activation->tab_strip_model()->GetWebContentsAt(
first_tab.value()));
base::RecordAction(
base::UserMetricsAction("TabGroups_SavedTabGroups_Focused"));
}
const TabStripModel* SavedTabGroupKeyedService::GetTabStripModelWithTabGroupId( const TabStripModel* SavedTabGroupKeyedService::GetTabStripModelWithTabGroupId(
const tab_groups::TabGroupId& local_group_id) { const tab_groups::TabGroupId& local_group_id) {
const Browser* const browser = const Browser* const browser =

@@ -114,11 +114,6 @@ class SavedTabGroupKeyedService : public KeyedService,
opened_web_contents_to_uuid, opened_web_contents_to_uuid,
const SavedTabGroup& saved_group); const SavedTabGroup& saved_group);
// Activates the first tab in saved group that is already opened when its
// button is pressed, If active tab exists in saved group, only activates
// window.
void FocusFirstTabOrWindowInOpenGroup(tab_groups::TabGroupId local_group_id);
// Returns a pointer to the TabStripModel which contains `local_group_id`. // Returns a pointer to the TabStripModel which contains `local_group_id`.
const TabStripModel* GetTabStripModelWithTabGroupId( const TabStripModel* GetTabStripModelWithTabGroupId(
const tab_groups::TabGroupId& local_group_id); const tab_groups::TabGroupId& local_group_id);

@@ -4,11 +4,16 @@
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h" #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h"
#include <numeric>
#include <unordered_set>
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/uuid.h" #include "base/uuid.h"
#include "chrome/app/vector_icons/vector_icons.h" #include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/favicon/favicon_utils.h" #include "chrome/browser/favicon/favicon_utils.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h" #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
@@ -17,6 +22,7 @@
#include "chrome/browser/ui/tabs/tab_group_theme.h" #include "chrome/browser/ui/tabs/tab_group_theme.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/pref_names.h" #include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h" #include "chrome/grit/generated_resources.h"
#include "components/saved_tab_groups/features.h" #include "components/saved_tab_groups/features.h"
@@ -273,6 +279,94 @@ std::vector<content::WebContents*> SavedTabGroupUtils::GetWebContentsesInGroup(
return contentses; return contentses;
} }
std::unordered_set<std::string> SavedTabGroupUtils::GetURLsInSavedTabGroup(
const tab_groups::SavedTabGroupKeyedService& saved_tab_group_service,
const base::Uuid& saved_id) {
const tab_groups::SavedTabGroup* const saved_group =
saved_tab_group_service.model()->Get(saved_id);
CHECK(saved_group);
std::unordered_set<std::string> saved_urls;
for (const tab_groups::SavedTabGroupTab& saved_tab :
saved_group->saved_tabs()) {
saved_urls.emplace(saved_tab.url().spec());
}
return saved_urls;
}
void SavedTabGroupUtils::MoveGroupToExistingWindow(
Browser* source_browser,
Browser* target_browser,
const tab_groups::TabGroupId& local_group_id,
const base::Uuid& saved_group_id) {
CHECK(source_browser);
CHECK(target_browser);
tab_groups::SavedTabGroupKeyedService* const service =
SavedTabGroupServiceFactory::GetForProfile(source_browser->profile());
CHECK(service);
// Find the grouped tabs in `source_browser`.
gfx::Range tabs_to_move = source_browser->tab_strip_model()
->group_model()
->GetTabGroup(local_group_id)
->ListTabs();
int num_tabs_to_move = tabs_to_move.length();
std::vector<int> tab_indicies_to_move(num_tabs_to_move);
std::iota(tab_indicies_to_move.begin(), tab_indicies_to_move.end(),
tabs_to_move.start());
// Disconnect the group and move the tabs to `target_browser`.
service->DisconnectLocalTabGroup(local_group_id);
chrome::MoveTabsToExistingWindow(source_browser, target_browser,
tab_indicies_to_move);
// Tabs should be in `target_browser` now. Regroup them.
int total_tabs = target_browser->tab_strip_model()->count();
int first_tab_moved = total_tabs - num_tabs_to_move;
std::vector<int> tabs_to_add_to_group(num_tabs_to_move);
std::iota(tabs_to_add_to_group.begin(), tabs_to_add_to_group.end(),
first_tab_moved);
// Add group the tabs using the same local id, and reconnect everything.
target_browser->tab_strip_model()->AddToGroupForRestore(tabs_to_add_to_group,
local_group_id);
service->ConnectLocalTabGroup(local_group_id, saved_group_id);
}
void SavedTabGroupUtils::FocusFirstTabOrWindowInOpenGroup(
tab_groups::TabGroupId local_group_id) {
Browser* browser_for_activation =
SavedTabGroupUtils::GetBrowserWithTabGroupId(local_group_id);
// Only activate the tab group's first tab, if it exists in any browser's
// tabstrip model and it is not in the active tab in the tab group.
CHECK(browser_for_activation);
TabGroup* tab_group =
browser_for_activation->tab_strip_model()->group_model()->GetTabGroup(
local_group_id);
std::optional<int> first_tab = tab_group->GetFirstTab();
std::optional<int> last_tab = tab_group->GetLastTab();
int active_index = browser_for_activation->tab_strip_model()->active_index();
CHECK(first_tab.has_value());
CHECK(last_tab.has_value());
CHECK_GE(active_index, 0);
if (active_index >= first_tab.value() && active_index <= last_tab) {
browser_for_activation->window()->Activate();
return;
}
browser_for_activation->ActivateContents(
browser_for_activation->tab_strip_model()->GetWebContentsAt(
first_tab.value()));
base::RecordAction(
base::UserMetricsAction("TabGroups_SavedTabGroups_Focused"));
}
// static // static
bool SavedTabGroupUtils::IsURLValidForSavedTabGroups(const GURL& gurl) { bool SavedTabGroupUtils::IsURLValidForSavedTabGroups(const GURL& gurl) {
return gurl.SchemeIsHTTPOrHTTPS() || gurl == GURL(chrome::kChromeUINewTabURL); return gurl.SchemeIsHTTPOrHTTPS() || gurl == GURL(chrome::kChromeUINewTabURL);

@@ -5,7 +5,10 @@
#ifndef CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_UTILS_H_ #ifndef CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_UTILS_H_
#define CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_UTILS_H_ #define CHROME_BROWSER_UI_TABS_SAVED_TAB_GROUPS_SAVED_TAB_GROUP_UTILS_H_
#include <unordered_set>
#include "base/uuid.h" #include "base/uuid.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_keyed_service.h"
#include "chrome/browser/ui/tabs/tab_group.h" #include "chrome/browser/ui/tabs/tab_group.h"
#include "components/pref_registry/pref_registry_syncable.h" #include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
@@ -88,7 +91,25 @@ class SavedTabGroupUtils {
static std::vector<content::WebContents*> GetWebContentsesInGroup( static std::vector<content::WebContents*> GetWebContentsesInGroup(
tab_groups::TabGroupId group_id); tab_groups::TabGroupId group_id);
// Returns whether the tab's URL is viable for saving in a saved tab group. // Returns the set of urls currently stored in the saved tab group.
static std::unordered_set<std::string> GetURLsInSavedTabGroup(
const tab_groups::SavedTabGroupKeyedService& saved_tab_group_service,
const base::Uuid& saved_id);
// Moves an open saved tab group from `source_browser` to `target_browser`.
static void MoveGroupToExistingWindow(
Browser* source_browser,
Browser* target_browser,
const tab_groups::TabGroupId& local_group_id,
const base::Uuid& saved_group_id);
// Activates the first tab in the saved group. If a tab in the group is
// already activated, then we focus the window the group belongs to instead.
static void FocusFirstTabOrWindowInOpenGroup(
tab_groups::TabGroupId local_group_id);
// Returns whether the tab's URL is viable for saving in a saved tab
// group.
static bool IsURLValidForSavedTabGroups(const GURL& gurl); static bool IsURLValidForSavedTabGroups(const GURL& gurl);
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);

@@ -253,7 +253,7 @@ IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperInteractiveTest,
browser2 = GetBrowser(1); browser2 = GetBrowser(1);
EXPECT_EQ(kTabCount, browser2->tab_strip_model()->count()); EXPECT_EQ(kTabCount, browser2->tab_strip_model()->count());
EXPECT_EQ(kTabCount - 1, browser2->tab_strip_model()->active_index()); EXPECT_EQ(0, browser2->tab_strip_model()->active_index());
// These tabs shouldn't want to be loaded. // These tabs shouldn't want to be loaded.
for (int tab_idx = 1; tab_idx < kTabCount - 1; ++tab_idx) { for (int tab_idx = 1; tab_idx < kTabCount - 1; ++tab_idx) {

@@ -41,6 +41,7 @@
#include "components/sessions/core/tab_restore_types.h" #include "components/sessions/core/tab_restore_types.h"
#include "components/tab_groups/tab_group_id.h" #include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h" #include "components/tab_groups/tab_group_visual_data.h"
namespace sessions { namespace sessions {
namespace { namespace {
@@ -514,53 +515,24 @@ std::vector<LiveTab*> TabRestoreServiceHelper::RestoreEntryById(
window.show_state, window.workspace, window.user_title, window.show_state, window.workspace, window.user_title,
window.extra_data); window.extra_data);
base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId> for (const auto& tab : window.tabs) {
new_group_ids; const bool first_tab = window.tabs[0]->id == tab->id;
for (size_t tab_i = 0; tab_i < window.tabs.size(); ++tab_i) {
Tab& tab = *window.tabs[tab_i];
// Relabel group IDs to prevent duplicating groups, e.g. if the same
// window is restored twice or a tab of the same ID is restored
// elsewhere. See crbug.com/1202102.
std::optional<tab_groups::TabGroupId> new_group;
if (tab.group) {
auto it = new_group_ids.find(*tab.group);
if (it == new_group_ids.end()) {
auto new_id = tab_groups::TabGroupId::GenerateNew();
// Ensure the new ID does not collide with an existing group,
// failing silently if it does. This is extremely unlikely,
// given group IDs are 128 bit randomly generated numbers.
if (client_->FindLiveTabContextWithGroup(new_id)) {
return std::vector<LiveTab*>();
}
it = new_group_ids.emplace(*tab.group, new_id).first;
}
new_group = it->second;
tab.group = new_group;
}
LiveTab* restored_tab = context->AddRestoredTab( LiveTab* restored_tab = context->AddRestoredTab(
tab, /*tab_index=*/context->GetTabCount(), *tab.get(), context->GetTabCount(), first_tab);
/*select=*/static_cast<int>(tab_i) == window.selected_tab_index);
if (restored_tab) { if (restored_tab) {
client_->OnTabRestored( client_->OnTabRestored(
tab.navigations.at(tab.current_navigation_index).virtual_url()); tab->navigations.at(tab->current_navigation_index)
.virtual_url());
live_tabs.push_back(restored_tab); live_tabs.push_back(restored_tab);
} }
} }
for (const auto& tab_group : window.tab_groups) { // Update all tabs to point to the correct context.
context->SetVisualDataForGroup(new_group_ids.at(tab_group.first),
tab_group.second);
}
// All the window's tabs had the same former browser_id.
if (auto browser_id = window.tabs[0]->browser_id) { if (auto browser_id = window.tabs[0]->browser_id) {
UpdateTabBrowserIDs(browser_id, context->GetSessionID()); UpdateTabBrowserIDs(browser_id, context->GetSessionID());
} }
} else { } else {
// Restore a single tab from the window. Find the tab that matches the // Restore a single tab from the window. Find the tab that matches the
// ID in the window and restore it. // ID in the window and restore it.
@@ -636,20 +608,9 @@ std::vector<LiveTab*> TabRestoreServiceHelper::RestoreEntryById(
// single tab within it. If the entry's ID matches the one to restore, // single tab within it. If the entry's ID matches the one to restore,
// then the entire group will be restored. // then the entire group will be restored.
if (entry_id_matches_restore_id) { if (entry_id_matches_restore_id) {
// Restore the first tab in the group with the given disposition and for (const auto& tab : group.tabs) {
// context. After that, subsequent tabs will automatically be restored LiveTab* restored_tab = context->AddRestoredTab(
// into the existing group. *tab.get(), context->GetTabCount(), group.tabs[0]->id == tab->id);
LiveTab* restored_tab = nullptr;
context =
RestoreTab(*group.tabs[0], context, disposition, &restored_tab);
live_tabs.push_back(restored_tab);
for (size_t i = 1; i < group.tabs.size(); ++i) {
// Restore the remaining tabs as background tabs, to keep the first
// tab in the group active.
context = RestoreTab(*group.tabs[i], context,
WindowOpenDisposition::NEW_BACKGROUND_TAB,
&restored_tab);
live_tabs.push_back(restored_tab); live_tabs.push_back(restored_tab);
} }
} else { } else {
@@ -661,12 +622,7 @@ std::vector<LiveTab*> TabRestoreServiceHelper::RestoreEntryById(
LiveTab* restored_tab = nullptr; LiveTab* restored_tab = nullptr;
context = RestoreTab(tab, context, disposition, &restored_tab); context = RestoreTab(tab, context, disposition, &restored_tab);
live_tabs.push_back(restored_tab); live_tabs.push_back(restored_tab);
DCHECK(ValidateGroup(group)); DCHECK(ValidateGroup(group));
group.tabs.erase(group.tabs.begin() + i);
if (group.tabs.empty())
entries_.erase(entry_iterator);
break; break;
} }
} }