[ 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:

committed by
Chromium LUCI CQ

parent
2961448b62
commit
ae5419e0b2
chrome/browser
performance_manager
sessions
ui
components/sessions/core
@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user