[go: nahoru, domu]

blob: 65496cbf9a1fd055753170c3b40e861d2f95c723 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/new_tab_page/modules/v2/tab_resumption/tab_resumption_page_handler.h"
#include <stddef.h>
#include <set>
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/new_tab_page/modules/v2/tab_resumption/tab_resumption.mojom.h"
#include "chrome/browser/new_tab_page/modules/v2/tab_resumption/tab_resumption_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/session_sync_service_factory.h"
#include "chrome/browser/ui/webui/ntp/new_tab_ui.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/history/core/browser/history_types.h"
#include "components/history/core/browser/mojom/history_types.mojom.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search/ntp_features.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_sessions/open_tabs_ui_delegate.h"
#include "components/sync_sessions/session_sync_service.h"
#include "content/public/browser/web_ui.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
#include "url/gurl.h"
using history::BrowsingHistoryService;
using history::HistoryService;
const size_t kCategoryBlockListCount = 18;
constexpr std::array<std::string_view, kCategoryBlockListCount>
kCategoryBlockList{"/g/11b76fyj2r", "/m/09lkz", "/m/012mj", "/m/01rbb",
"/m/02px0wr", "/m/028hh", "/m/034qg", "/m/034dj",
"/m/0jxxt", "/m/015fwp", "/m/04shl0", "/m/01h6rj",
"/m/05qt0", "/m/06gqm", "/m/09l0j_", "/m/01pxgq",
"/m/0chbx", "/m/02c66t"};
namespace {
// Name of preference to track list of dismissed tabs.
const char kDismissedTabsPrefName[] = "NewTabPage.TabResumption.DismissedTabs";
std::u16string FormatRelativeTime(const base::Time& time) {
// Return a time like "1 hour ago", "2 days ago", etc.
base::Time now = base::Time::Now();
// TimeFormat does not support negative TimeDelta values, so then we use 0.
return ui::TimeFormat::Simple(ui::TimeFormat::FORMAT_ELAPSED,
ui::TimeFormat::LENGTH_SHORT,
now < time ? base::TimeDelta() : now - time);
}
// Helper method to create mojom tab objects from SessionTab objects.
history::mojom::TabPtr SessionTabToMojom(
const ::sessions::SessionTab& tab,
const syncer::DeviceInfo::FormFactor device_type,
const std::string& session_name) {
if (tab.navigations.empty()) {
return nullptr;
}
int selected_index = std::min(tab.current_navigation_index,
static_cast<int>(tab.navigations.size() - 1));
const ::sessions::SerializedNavigationEntry& current_navigation =
tab.navigations.at(selected_index);
GURL tab_url = current_navigation.virtual_url();
if (!tab_url.is_valid() || tab_url.spec() == chrome::kChromeUINewTabURL) {
return nullptr;
}
auto tab_mojom = history::mojom::Tab::New();
tab_mojom->device_type =
history::mojom::DeviceType(static_cast<int>(device_type));
base::Value::Dict dictionary;
NewTabUI::SetUrlTitleAndDirection(&dictionary, current_navigation.title(),
tab_url);
tab_mojom->session_name = session_name;
tab_mojom->url = GURL(*dictionary.FindString("url"));
tab_mojom->title = *dictionary.FindString("title");
auto relative_time = base::Time::Now() - tab.timestamp;
tab_mojom->relative_time = relative_time;
if (relative_time.InSeconds() < 60) {
tab_mojom->relative_time_text = l10n_util::GetStringUTF8(
IDS_NTP_MODULES_TAB_RESUMPTION_RECENTLY_OPENED);
} else {
tab_mojom->relative_time_text =
base::UTF16ToUTF8(FormatRelativeTime(tab.timestamp));
}
return tab_mojom;
}
// Helper method to append mojom tab objects from SessionWindow objects.
void SessionWindowToMojom(std::vector<history::mojom::TabPtr>& tabs_mojom,
const ::sessions::SessionWindow& window,
const syncer::DeviceInfo::FormFactor device_type,
const std::string& session_name) {
if (window.tabs.empty()) {
return;
}
for (const std::unique_ptr<sessions::SessionTab>& tab : window.tabs) {
history::mojom::TabPtr tab_mojom =
SessionTabToMojom(*tab.get(), device_type, session_name);
if (tab_mojom) {
tabs_mojom.push_back(std::move(tab_mojom));
}
}
}
// Helper method to create a list of mojom tab objects from Session objects.
std::vector<history::mojom::TabPtr> SessionToMojom(
const sync_sessions::SyncedSession* session) {
std::vector<history::mojom::TabPtr> tabs_mojom;
const syncer::DeviceInfo::FormFactor device_type =
session->GetDeviceFormFactor();
const std::string& session_name = session->GetSessionName();
// Order tabs by visual order within window.
for (const auto& window_pair : session->windows) {
SessionWindowToMojom(tabs_mojom, window_pair.second->wrapped_window,
device_type, session_name);
}
return tabs_mojom;
}
} // namespace
TabResumptionPageHandler::TabResumptionPageHandler(
mojo::PendingReceiver<ntp::tab_resumption::mojom::PageHandler>
pending_page_handler,
content::WebContents* web_contents)
: profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
web_contents_(web_contents),
page_handler_(this, std::move(pending_page_handler)),
visibility_threshold_(
static_cast<float>(base::GetFieldTrialParamByFeatureAsDouble(
ntp_features::kNtpTabResumptionModule,
ntp_features::kNtpTabResumptionModuleVisibilityThresholdDataParam,
/*Default value for visibility threshold*/ 0.5))),
categories_blocklist_(GetTabResumptionCategories(
ntp_features::kNtpTabResumptionModuleCategoriesBlocklistParam,
{kCategoryBlockList.begin(), kCategoryBlockListCount})),
time_limit_(base::GetFieldTrialParamByFeatureAsInt(
ntp_features::kNtpTabResumptionModuleTimeLimit,
ntp_features::kNtpTabResumptionModuleTimeLimitParam,
/*Default value for time limit*/ 24)) {
DCHECK(profile_);
DCHECK(web_contents_);
}
TabResumptionPageHandler::~TabResumptionPageHandler() = default;
void TabResumptionPageHandler::OnQueryURLsComplete(
std::vector<history::mojom::TabPtr> tabs,
GetTabsCallback callback,
std::vector<history::QueryURLResult> results) {
history::VisitVector visit_rows;
for (auto result : results) {
for (auto visit : result.visits) {
visit_rows.push_back(visit);
}
}
auto* history_service = HistoryServiceFactory::GetForProfile(
profile_, ServiceAccessType::EXPLICIT_ACCESS);
history_service->ToAnnotatedVisits(
visit_rows,
/*compute_redirect_chain_start_properties=*/false,
base::BindOnce(&TabResumptionPageHandler::OnAnnotatedVisits,
weak_ptr_factory_.GetWeakPtr(), std::move(tabs),
std::move(callback)),
&task_tracker_);
}
void TabResumptionPageHandler::OnAnnotatedVisits(
std::vector<history::mojom::TabPtr> tabs,
GetTabsCallback callback,
const std::vector<history::AnnotatedVisit> annotated_visits) {
std::vector<history::mojom::TabPtr> scored_tabs;
std::set<int> scored_tab_indices;
for (const auto& annotated_visit : annotated_visits) {
float visibility_score =
annotated_visit.content_annotations.model_annotations.visibility_score;
/* If score is -1, it has not been evaluated for visibility */
if (visibility_score < visibility_threshold_ && visibility_score >= 0) {
continue;
}
if (IsVisitInCategories(annotated_visit, categories_blocklist_)) {
continue;
}
for (size_t i = 0; i < tabs.size(); i++) {
if (annotated_visit.url_row.url() == tabs[i]->url &&
scored_tab_indices.find(i) == scored_tab_indices.end()) {
scored_tab_indices.insert(i);
break;
}
}
}
bool new_url_found = false;
for (auto index : scored_tab_indices) {
if (IsNewURL(tabs[index]->url)) {
new_url_found = true;
}
scored_tabs.push_back(std::move(tabs[index]));
}
// Bail if module is still dismissed.
if (profile_->GetPrefs()->GetList(kDismissedTabsPrefName).size() > 0 &&
!new_url_found) {
std::move(callback).Run(std::vector<history::mojom::TabPtr>());
return;
}
std::sort(scored_tabs.begin(), scored_tabs.end(), CompareTabsByTime);
std::move(callback).Run(std::move(scored_tabs));
}
void TabResumptionPageHandler::GetTabs(GetTabsCallback callback) {
const std::string fake_data_param = base::GetFieldTrialParamValueByFeature(
ntp_features::kNtpTabResumptionModule,
ntp_features::kNtpTabResumptionModuleDataParam);
if (!fake_data_param.empty()) {
std::vector<history::mojom::TabPtr> tabs_mojom;
const int kSampleSessionsCount = 3;
for (int i = 0; i < kSampleSessionsCount; i++) {
auto session_tabs_mojom =
SessionToMojom(SampleSession("Test Session Name", 3, 1).get());
for (auto& tab_mojom : session_tabs_mojom) {
tabs_mojom.push_back(std::move(tab_mojom));
}
}
std::move(callback).Run(std::move(tabs_mojom));
return;
}
auto tabs_mojom = GetForeignTabs();
std::vector<GURL> urls;
for (const auto& tab : tabs_mojom) {
urls.push_back(tab->url);
}
if (urls.empty()) {
std::move(callback).Run({});
return;
}
auto* history_service = HistoryServiceFactory::GetForProfile(
profile_, ServiceAccessType::EXPLICIT_ACCESS);
history_service->QueryURLs(
urls, /*want_visits=*/true,
base::BindOnce(&TabResumptionPageHandler::OnQueryURLsComplete,
weak_ptr_factory_.GetWeakPtr(), std::move(tabs_mojom),
std::move(callback)),
&task_tracker_);
}
// static
void TabResumptionPageHandler::RegisterProfilePrefs(
PrefRegistrySimple* registry) {
registry->RegisterListPref(kDismissedTabsPrefName, base::Value::List());
}
// static
sync_sessions::OpenTabsUIDelegate*
TabResumptionPageHandler::GetOpenTabsUIDelegate() {
sync_sessions::SessionSyncService* service =
SessionSyncServiceFactory::GetInstance()->GetForProfile(profile_);
return service ? service->GetOpenTabsUIDelegate() : nullptr;
}
std::vector<history::mojom::TabPtr> TabResumptionPageHandler::GetForeignTabs() {
sync_sessions::OpenTabsUIDelegate* open_tabs = GetOpenTabsUIDelegate();
std::vector<raw_ptr<const sync_sessions::SyncedSession, VectorExperimental>>
sessions;
std::vector<history::mojom::TabPtr> tabs_mojom;
if (open_tabs && open_tabs->GetAllForeignSessions(&sessions)) {
// Note: we don't own the SyncedSessions themselves.
for (size_t i = 0; i < sessions.size(); ++i) {
const sync_sessions::SyncedSession* session(sessions[i]);
auto session_tabs_mojom = SessionToMojom(session);
for (auto& tab_mojom : session_tabs_mojom) {
if (tab_mojom && (tab_mojom->relative_time).InHours() < time_limit_ &&
!tab_mojom->url.is_empty()) {
tabs_mojom.push_back(std::move(tab_mojom));
}
}
}
}
return tabs_mojom;
}
void TabResumptionPageHandler::DismissModule(const std::vector<GURL>& urls) {
base::Value::List url_list;
for (const auto& url : urls) {
url_list.Append(url.spec());
}
profile_->GetPrefs()->SetList(kDismissedTabsPrefName, std::move(url_list));
}
void TabResumptionPageHandler::RestoreModule() {
profile_->GetPrefs()->SetList(kDismissedTabsPrefName, base::Value::List());
}
bool TabResumptionPageHandler::IsNewURL(GURL url) {
const base::Value::List& cached_urls =
profile_->GetPrefs()->GetList(kDismissedTabsPrefName);
auto it = std::find_if(cached_urls.begin(), cached_urls.end(),
[url](const base::Value& cached_url) {
return cached_url.GetString() == url.spec();
});
if (it == cached_urls.end()) {
return true;
}
return false;
}