blob: 9e6a5acd4836f2471582cbb4edfd29c573f12e88 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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 "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 "chrome/browser/profiles/profile.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_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_live_tab_context.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 "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/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 "url/gurl.h"
namespace chrome {
namespace {
const char kBrowserTabRestorerKey[] = "BrowserTabRestorer";
// BrowserTabRestorer is responsible for restoring a tab when the
// sessions::TabRestoreService finishes loading. A TabRestoreService is
// associated with a
// single Browser and deletes itself if the Browser is destroyed.
// BrowserTabRestorer is installed on the Profile (by way of user data), only
// one instance is created per profile at a time.
class BrowserTabRestorer : public sessions::TabRestoreServiceObserver,
public BrowserListObserver,
public base::SupportsUserData::Data {
public:
BrowserTabRestorer(const BrowserTabRestorer&) = delete;
BrowserTabRestorer& operator=(const BrowserTabRestorer&) = delete;
~BrowserTabRestorer() override;
static void CreateIfNecessary(Browser* browser);
private:
explicit BrowserTabRestorer(Browser* browser);
// TabRestoreServiceObserver:
void TabRestoreServiceDestroyed(
sessions::TabRestoreService* service) override;
void TabRestoreServiceLoaded(sessions::TabRestoreService* service) override;
// BrowserListObserver:
void OnBrowserRemoved(Browser* browser) override;
raw_ptr<Browser> browser_;
raw_ptr<sessions::TabRestoreService> tab_restore_service_;
};
BrowserTabRestorer::~BrowserTabRestorer() {
tab_restore_service_->RemoveObserver(this);
BrowserList::RemoveObserver(this);
}
// static
void BrowserTabRestorer::CreateIfNecessary(Browser* browser) {
DCHECK(browser);
if (browser->profile()->GetUserData(kBrowserTabRestorerKey))
return; // Only allow one restore for a given profile at a time.
// BrowserTabRestorer is deleted at the appropriate time.
new BrowserTabRestorer(browser);
}
BrowserTabRestorer::BrowserTabRestorer(Browser* browser)
: browser_(browser),
tab_restore_service_(
TabRestoreServiceFactory::GetForProfile(browser->profile())) {
DCHECK(tab_restore_service_);
DCHECK(!tab_restore_service_->IsLoaded());
tab_restore_service_->AddObserver(this);
BrowserList::AddObserver(this);
browser_->profile()->SetUserData(kBrowserTabRestorerKey,
base::WrapUnique(this));
tab_restore_service_->LoadTabsFromLastSession();
}
void BrowserTabRestorer::TabRestoreServiceDestroyed(
sessions::TabRestoreService* service) {}
void BrowserTabRestorer::TabRestoreServiceLoaded(
sessions::TabRestoreService* service) {
RestoreTab(browser_);
// This deletes us.
browser_->profile()->SetUserData(kBrowserTabRestorerKey, nullptr);
}
void BrowserTabRestorer::OnBrowserRemoved(Browser* browser) {
// This deletes us.
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;
}
void AddMissingTabToGroup(
tab_groups::SavedTabGroupKeyedService& saved_tab_group_service,
const base::Uuid& saved_id,
const tab_groups::TabGroupId& group_id,
const sessions::TabRestoreService::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())) {
// Add the missing tab to the saved group if it doesn't exist.
tab_groups::SavedTabGroupTab saved_tab_group_tab(
entry.virtual_url(), entry.title(), saved_id, std::nullopt);
// TODO(dljames): Find a way to load favicons and set it here.
saved_tab_group_service.model()->AddTabToGroupLocally(
saved_id, std::move(saved_tab_group_tab));
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 OpenSavedTabGroup(sessions::TabRestoreService& tab_restore_service,
const sessions::TabRestoreService::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_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;
const tab_groups::TabGroupId& group_id = group.group_id;
const tab_groups::TabGroupVisualData& visual_data = group.visual_data;
// 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());
for (const std::unique_ptr<sessions::TabRestoreService::Tab>& tab :
group.tabs) {
AddMissingTabToGroup(*saved_tab_group_service, saved_id.value(), group_id,
*tab.get(), &urls_in_saved_group);
}
// Open the group.
std::optional<tab_groups::TabGroupId> new_group_id =
saved_tab_group_service->OpenSavedTabGroupInBrowser(browser,
saved_id.value());
CHECK(new_group_id.has_value());
UpdateGroupVisualData(new_group_id.value(), visual_data);
// Clean up TabRestoreService.
tab_restore_service.RemoveEntryById(session_id);
}
void OpenSavedTabGroupTab(sessions::TabRestoreService& tab_restore_service,
const sessions::TabRestoreService::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_id.has_value() &&
saved_tab_group_service->model()->Contains(tab.saved_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_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 (!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_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()
->group_model()
->GetTabGroup(group_id.value())
->GetFirstTab()
.value();
browser->tab_strip_model()->AddToExistingGroup(
{index}, saved_group->local_group_id().value(), /*add_to_end=*/true);
return;
}
// 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(*saved_tab_group_service, saved_id.value(),
group_id.value(), tab, &urls_in_saved_group);
// Open the group.
std::optional<tab_groups::TabGroupId> new_group_id =
saved_tab_group_service->OpenSavedTabGroupInBrowser(browser,
saved_id.value());
CHECK(new_group_id.has_value());
if (visual_data.has_value()) {
UpdateGroupVisualData(new_group_id.value(), visual_data.value());
}
// Clean up TabRestoreService.
tab_restore_service.RemoveEntryById(session_id);
}
} // namespace
void RestoreTab(Browser* browser) {
base::RecordAction(base::UserMetricsAction("RestoreTab"));
sessions::TabRestoreService* service =
TabRestoreServiceFactory::GetForProfile(browser->profile());
if (!service) {
return;
}
if (service->IsLoaded()) {
if (!tab_groups::IsTabGroupsSaveV2Enabled() || service->entries().empty()) {
// Restore normally.
service->RestoreMostRecentEntry(browser->live_tab_context());
return;
}
const std::unique_ptr<sessions::TabRestoreService::Entry>&
most_recent_entry = service->entries().front();
switch (most_recent_entry->type) {
case sessions::TabRestoreService::TAB: {
OpenSavedTabGroupTab(*service,
static_cast<sessions::TabRestoreService::Tab&>(
*most_recent_entry.get()),
browser);
return;
}
case sessions::TabRestoreService::WINDOW: {
// TODO(dljames): Handle Window Entries. Restore windows normally for
// now.
service->RestoreMostRecentEntry(browser->live_tab_context());
return;
}
case sessions::TabRestoreService::GROUP: {
OpenSavedTabGroup(*service,
static_cast<sessions::TabRestoreService::Group&>(
*most_recent_entry.get()),
browser);
return;
}
}
NOTREACHED();
}
BrowserTabRestorer::CreateIfNecessary(browser);
}
} // namespace chrome