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

@ -453,7 +453,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindowAndTab) {
// Restore the first window. The expected_tabstrip_index (second argument)
// 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);
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
// group should be in its original position.
// group should be at the end of the tabstrip.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroup) {
AddSomeTabs(browser(), 3);
tab_groups::TabGroupId group =
browser()->tab_strip_model()->AddToNewGroup({1, 2});
CloseGroup(group);
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 1));
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 2));
EXPECT_EQ(browser()
->tab_strip_model()
->group_model()
->GetTabGroup(group)
->ListTabs(),
gfx::Range(1, 3));
gfx::Range(2, 4));
}
// 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) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
AddSomeTabs(browser(), 3);
@ -591,7 +591,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTabThenGroup) {
CloseTab(1);
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));
EXPECT_EQ(browser()
@ -599,7 +599,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupedTabThenGroup) {
->group_model()
->GetTabGroup(group)
->ListTabs(),
gfx::Range(0, 3));
gfx::Range(1, 4));
}
// 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);
ASSERT_EQ(2u, active_browser_list_->size());
// Close the original group, which closes the original window.
CloseGroup(group);
ui_test_utils::WaitForBrowserToClose();
// Close the original group, which closes the original window. We spawn a new
// tab if the group is the only element in the browser and is closing. This
// prevents the browser from actually closing, so we close it manually
// instead.
CloseBrowserSynchronously(browser());
EXPECT_EQ(1u, active_browser_list_->size());
// Restore the original group, which should create a new window.
@ -732,7 +734,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreGroupWithUnloadHandlerRejected) {
->group_model()
->GetTabGroup(group)
->ListTabs(),
gfx::Range(1, 3));
gfx::Range(2, 4));
EXPECT_EQ(browser()->tab_strip_model()->count(), 4);
// 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_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 =
browser->tab_strip_model()->GetWebContentsAt(initial_tab_count + 1);
EnsureTabFinishedRestoring(restored_tab);
@ -1178,18 +1180,18 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest,
browser2 = GetBrowser(1);
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.
EnsureTabFinishedRestoring(
browser2->tab_strip_model()->GetWebContentsAt(tabs_count - 1));
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
// task that could have an impact on the expectations below.
content::RunAllPendingInMessageLoop();
// 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);
EXPECT_FALSE(contents->IsLoading());
EXPECT_TRUE(contents->GetController().NeedsReload());
@ -1261,7 +1263,7 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreWindowWithName) {
EXPECT_EQ(1u, active_browser_list_->size());
// Restore the first browser.
ASSERT_NO_FATAL_FAILURE(RestoreTab(1, 1));
ASSERT_NO_FATAL_FAILURE(RestoreTab(1, 0));
Browser* browser = GetBrowser(1);
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
// back expanded with its metadata.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreCollapsedGroupTab_ExpandsGroup) {
// back collapsed with its metadata.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreCollapsedGroupTab) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
const int tab_count = AddSomeTabs(browser(), 1);
@ -1336,13 +1338,12 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreCollapsedGroupTab_ExpandsGroup) {
ASSERT_TRUE(data);
EXPECT_EQ(data->title(), visual_data.title());
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.
IN_PROC_BROWSER_TEST_F(TabRestoreTest,
RestoreTabIntoCollapsedGroup_ExpandsGroup) {
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoCollapsedGroup) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
const int tab_count = AddSomeTabs(browser(), 2);
@ -1374,11 +1375,11 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest,
->visual_data();
EXPECT_EQ(data->title(), visual_data.title());
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
// place the group back without updating the metadata.
// place the group back and update the metadata.
IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoGroup) {
ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups());
@ -1408,8 +1409,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreTest, RestoreTabIntoGroup) {
->GetTabGroupForTab(closed_tab_index)
.value());
const tab_groups::TabGroupVisualData* data = group->visual_data();
EXPECT_EQ(data->title(), visual_data_2.title());
EXPECT_EQ(data->color(), visual_data_2.color());
EXPECT_EQ(data->title(), visual_data_1.title());
EXPECT_EQ(data->color(), visual_data_1.color());
}
// 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
// histogram.
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 1));
ASSERT_NO_FATAL_FAILURE(RestoreGroup(group, 0, 2));
EXPECT_EQ(
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());
// The group ID should be new.
EXPECT_NE(original_group,
EXPECT_EQ(original_group,
third_browser->tab_strip_model()->GetTabGroupForTab(1));
}
@ -2109,7 +2110,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest, RestoreSavedGroup) {
ASSERT_TRUE(service->model()->Contains(group));
// Close the group.
browser()->tab_strip_model()->CloseAllTabsInGroup(group);
// browser()->tab_strip_model()->CloseAllTabsInGroup(group);
CloseGroup(group);
// Restore the group.
chrome::RestoreTab(browser());
@ -2136,10 +2138,10 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest, RestoreSavedGroup) {
gfx::Range(1, 3));
}
// Close a saved group, then restore it. The group should continue to be saved
// and update its color to match the restored entry.
// Close a saved group and restore it. The group should not have updated its
// color to match the restored entry.
IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
RestoreSavedGroupUpdatesColor) {
RestoreSavedGroupDoesNotUpdateVisualData) {
AddTabs(browser(), 2);
tab_groups::SavedTabGroupKeyedService* service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile(
@ -2165,20 +2167,23 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
// Close the group.
browser()->tab_strip_model()->CloseAllTabsInGroup(group);
// Update the color of the saved group.
tab_groups::TabGroupVisualData new_visual_data(
u"This is a new title", tab_groups::TabGroupColorId::kCyan);
// Update the visual data of the saved group.
std::u16string new_title = u"This is a new title";
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);
// Restore it.
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 =
service->model()->Get(saved_group_id);
CHECK(saved_group);
EXPECT_EQ(original_title, saved_group->title());
EXPECT_EQ(original_color, saved_group->color());
EXPECT_NE(original_title, saved_group->title());
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
@ -2443,6 +2448,8 @@ IN_PROC_BROWSER_TEST_F(TabRestoreSavedGroupsTest,
chrome::RestoreTab(browser());
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.
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 =
second_browser->tab_strip_model()->group_model()->ListTabGroups();
// Verify there is only 1 saved group, the first browser has 2 tabs (tabs no
// in the group), and the second browser has 3 tabs (1 tab group).
// Verify there is only 1 saved group, the first browser has 4 tabs (how it
// was originally), and the second browser has 1 tab (new tab page).
EXPECT_EQ(1, service->model()->Count());
EXPECT_EQ(2, first_browser->tab_strip_model()->count());
EXPECT_EQ(3, second_browser->tab_strip_model()->count());
EXPECT_EQ(4, first_browser->tab_strip_model()->count());
EXPECT_EQ(1, second_browser->tab_strip_model()->count());
EXPECT_TRUE(first_browser_group_ids.empty());
EXPECT_EQ(1u, second_browser_group_ids.size());
EXPECT_TRUE(service->model()->Contains(second_browser_group_ids[0]));
EXPECT_TRUE(second_browser_group_ids.empty());
EXPECT_EQ(1u, first_browser_group_ids.size());
EXPECT_TRUE(service->model()->Contains(first_browser_group_ids[0]));
EXPECT_TRUE(service->model()->Contains(saved_group_id));
tab_groups::SavedTabGroup saved_group =

@ -5,8 +5,10 @@
#include "chrome/browser/ui/browser_live_tab_context.h"
#include <memory>
#include <numeric>
#include <optional>
#include <utility>
#include <vector>
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
@ -17,6 +19,7 @@
#include "chrome/browser/browser_features.h"
#include "chrome/browser/profiles/profile.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_commands.h"
#include "chrome/browser/ui/browser_finder.h"
@ -25,6 +28,7 @@
#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"
@ -35,11 +39,15 @@
#include "components/saved_tab_groups/saved_tab_group.h"
#include "components/sessions/content/content_live_tab.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/tab_restore_service.h"
#include "components/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
#include "content/public/browser/navigation_controller.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)
#include "chrome/browser/sessions/tab_loader.h"
@ -198,6 +206,11 @@ sessions::LiveTab* BrowserLiveTabContext::AddRestoredTab(
const sessions::tab_restore::Tab& tab,
int tab_index,
bool select) {
tab_groups::SavedTabGroupKeyedService* saved_tab_group_service =
tab_groups::SavedTabGroupServiceFactory::GetForProfile(
browser_->profile());
CHECK(saved_tab_group_service);
SessionStorageNamespace* storage_namespace =
tab.platform_data
? static_cast<const sessions::ContentPlatformSpecificTabData*>(
@ -205,26 +218,77 @@ sessions::LiveTab* BrowserLiveTabContext::AddRestoredTab(
->session_storage_namespace()
: nullptr;
TabGroupModel* group_model = browser_->tab_strip_model()->group_model();
const bool first_tab_in_group =
group_model && tab.group.has_value() &&
!group_model->ContainsTabGroup(tab.group.value());
std::optional<tab_groups::TabGroupId> group_id = tab.group;
std::optional<base::Uuid> saved_group_id = tab.saved_group_id;
content::WebContents* web_contents = nullptr;
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(
browser_, tab.navigations, tab_index, tab.normalized_navigation_index(),
tab.extension_app_id, tab.group, select, tab.pinned, base::TimeTicks(),
storage_namespace, tab.user_agent_override, tab.extra_data,
false /* from_session_restore */);
if (is_grouped_tab_unsaved || group_deleted_from_model) {
browser_->live_tab_context()->SetVisualDataForGroup(
group_id.value(), tab.group_visual_data.value());
// Only update the metadata if the group doesn't already exist since the
// existing group has the latest metadata, which may have changed from the
// time the tab was closed.
if (first_tab_in_group) {
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);
// Save the group if it was not saved.
if (!saved_tab_group_service->model()->Contains(group_id.value()) &&
tab_groups::IsTabGroupsSaveV2Enabled()) {
saved_tab_group_service->SaveGroup(tab.group.value());
}
}
} 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)
@ -239,11 +303,13 @@ sessions::LiveTab* BrowserLiveTabContext::AddRestoredTab(
DCHECK(web_contents->GetController().NeedsReload());
DCHECK(!web_contents->IsLoading());
}
std::vector<TabLoader::RestoredTab> restored_tabs;
restored_tabs.emplace_back(web_contents, is_active,
!tab.extension_app_id.empty(), tab.pinned,
tab.group);
group_id);
TabLoader::RestoreTabs(restored_tabs, base::TimeTicks::Now());
#else // BUILDFLAG(ENABLE_SESSION_SERVICE)
// Load the tab manually if there is no TabLoader.
web_contents->GetController().LoadIfNecessary();

@ -11,6 +11,7 @@
#include "base/memory/raw_ptr.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_visual_data.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
// found in the LICENSE file.
#include <memory>
#include <optional>
#include <unordered_set>
#include <vector>
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.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/sessions/tab_restore_service_factory.h"
#include "chrome/browser/ui/browser.h"
@ -21,29 +14,8 @@
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.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_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 {
@ -125,378 +97,6 @@ void BrowserTabRestorer::OnBrowserRemoved(Browser* browser) {
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
void RestoreTab(Browser* browser) {
@ -509,39 +109,8 @@ void RestoreTab(Browser* browser) {
}
if (service->IsLoaded()) {
if (!tab_groups::IsTabGroupsSaveV2Enabled() || service->entries().empty()) {
// Restore normally.
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();
service->RestoreMostRecentEntry(browser->live_tab_context());
return;
}
BrowserTabRestorer::CreateIfNecessary(browser);

@ -108,8 +108,8 @@ IN_PROC_BROWSER_TEST_F(BrowserTabRestoreTest, RecentTabsMenuTabDisposition) {
}
}
// The middle tab only should have visible disposition.
CheckVisbility(restored_browser->tab_strip_model(), 1);
// Only the first tab should have visible disposition.
CheckVisbility(restored_browser->tab_strip_model(), 0);
}
// 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.
CheckVisbility(browser->tab_strip_model(), 1);
// Only the first tab should have visible disposition.
CheckVisbility(browser->tab_strip_model(), 0);
}

@ -124,7 +124,8 @@ SavedTabGroupKeyedService::OpenSavedTabGroupInBrowser(
// Activate the first tab in a group if it is already open.
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();
}
@ -484,38 +485,6 @@ SavedTabGroupKeyedService::GetWebContentsToTabGuidMappingForOpening(
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 tab_groups::TabGroupId& local_group_id) {
const Browser* const browser =

@ -114,11 +114,6 @@ class SavedTabGroupKeyedService : public KeyedService,
opened_web_contents_to_uuid,
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`.
const TabStripModel* GetTabStripModelWithTabGroupId(
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 <numeric>
#include <unordered_set>
#include "base/metrics/user_metrics.h"
#include "base/strings/utf_string_conversions.h"
#include "base/uuid.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/favicon/favicon_utils.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_navigator.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_strip_model.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/grit/generated_resources.h"
#include "components/saved_tab_groups/features.h"
@ -273,6 +279,94 @@ std::vector<content::WebContents*> SavedTabGroupUtils::GetWebContentsesInGroup(
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
bool SavedTabGroupUtils::IsURLValidForSavedTabGroups(const GURL& gurl) {
return gurl.SchemeIsHTTPOrHTTPS() || gurl == GURL(chrome::kChromeUINewTabURL);

@ -5,7 +5,10 @@
#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_
#include <unordered_set>
#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 "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
@ -88,7 +91,25 @@ class SavedTabGroupUtils {
static std::vector<content::WebContents*> GetWebContentsesInGroup(
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 void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);

@ -253,7 +253,7 @@ IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperInteractiveTest,
browser2 = GetBrowser(1);
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.
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/tab_groups/tab_group_id.h"
#include "components/tab_groups/tab_group_visual_data.h"
namespace sessions {
namespace {
@ -514,53 +515,24 @@ std::vector<LiveTab*> TabRestoreServiceHelper::RestoreEntryById(
window.show_state, window.workspace, window.user_title,
window.extra_data);
base::flat_map<tab_groups::TabGroupId, tab_groups::TabGroupId>
new_group_ids;
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;
}
for (const auto& tab : window.tabs) {
const bool first_tab = window.tabs[0]->id == tab->id;
LiveTab* restored_tab = context->AddRestoredTab(
tab, /*tab_index=*/context->GetTabCount(),
/*select=*/static_cast<int>(tab_i) == window.selected_tab_index);
*tab.get(), context->GetTabCount(), first_tab);
if (restored_tab) {
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);
}
}
for (const auto& tab_group : window.tab_groups) {
context->SetVisualDataForGroup(new_group_ids.at(tab_group.first),
tab_group.second);
}
// All the window's tabs had the same former browser_id.
// Update all tabs to point to the correct context.
if (auto browser_id = window.tabs[0]->browser_id) {
UpdateTabBrowserIDs(browser_id, context->GetSessionID());
}
} else {
// Restore a single tab from the window. Find the tab that matches the
// 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,
// then the entire group will be restored.
if (entry_id_matches_restore_id) {
// Restore the first tab in the group with the given disposition and
// context. After that, subsequent tabs will automatically be restored
// into the existing group.
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);
for (const auto& tab : group.tabs) {
LiveTab* restored_tab = context->AddRestoredTab(
*tab.get(), context->GetTabCount(), group.tabs[0]->id == tab->id);
live_tabs.push_back(restored_tab);
}
} else {
@ -661,12 +622,7 @@ std::vector<LiveTab*> TabRestoreServiceHelper::RestoreEntryById(
LiveTab* restored_tab = nullptr;
context = RestoreTab(tab, context, disposition, &restored_tab);
live_tabs.push_back(restored_tab);
DCHECK(ValidateGroup(group));
group.tabs.erase(group.tabs.begin() + i);
if (group.tabs.empty())
entries_.erase(entry_iterator);
break;
}
}