// Copyright 2015 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/ui/webui/history/browsing_history_handler.h" #include #include #include #include "base/check_deref.h" #include "base/check_op.h" #include "base/containers/flat_map.h" #include "base/feature_list.h" #include "base/functional/bind.h" #include "base/functional/callback_helpers.h" #include "base/i18n/rtl.h" #include "base/i18n/time_formatting.h" #include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/time/default_clock.h" #include "base/time/time.h" #include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/critical_actions/critical_action_factory.h" #include "chrome/browser/critical_actions/critical_action_ui_utils.h" #include "chrome/browser/favicon/large_icon_service_factory.h" #include "chrome/browser/history/history_service_factory.h" #include "chrome/browser/history/history_utils.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/signin/account_preview_data_service_factory.h" #include "chrome/browser/signin/chrome_signin_pref_names.h" #include "chrome/browser/signin/identity_manager_factory.h" #include "chrome/browser/signin/signin_ui_util.h" #include "chrome/browser/supervised_user/supervised_user_url_filtering_service_factory.h" #include "chrome/browser/sync/device_info_sync_service_factory.h" #include "chrome/browser/sync/sync_service_factory.h" #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/browser/ui/browser_window/public/global_browser_collection.h" #include "chrome/browser/ui/chrome_pages.h" #include "chrome/browser/ui/hats/hats_service.h" #include "chrome/browser/ui/hats/hats_service_factory.h" #include "chrome/browser/ui/hats/survey_config.h" #include "chrome/browser/ui/profiles/profile_view_utils.h" #include "chrome/browser/ui/url_identity.h" #include "chrome/browser/ui/webui/favicon_source.h" #include "chrome/browser/ui/webui/signin/signin_utils.h" #include "chrome/browser/ui/webui/top_chrome/top_chrome_web_ui_controller.h" #include "chrome/common/buildflags.h" #include "chrome/common/chrome_features.h" #include "chrome/common/pref_names.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/browser/bookmark_utils.h" #include "components/critical_actions/core/browser/critical_action_service.h" #include "components/critical_actions/core/browser/critical_action_types.h" #include "components/critical_actions/core/browser/features.h" #include "components/favicon/core/fallback_url_util.h" #include "components/favicon/core/large_icon_service.h" #include "components/favicon_base/favicon_url_parser.h" #include "components/history/core/browser/features.h" #include "components/history_clusters/core/config.h" #include "components/history_clusters/core/features.h" #include "components/history_clusters/core/history_clusters_prefs.h" #include "components/history_embeddings/core/history_embeddings_features.h" #include "components/keyed_service/core/service_access_type.h" #include "components/prefs/pref_service.h" #include "components/query_parser/snippet.h" #include "components/signin/public/base/signin_pref_names.h" #include "components/signin/public/base/signin_prefs.h" #include "components/signin/public/identity_manager/account_info.h" #include "components/signin/public/identity_manager/identity_manager.h" #include "components/strings/grit/components_strings.h" #include "components/supervised_user/core/browser/supervised_user_url_filtering_service.h" #include "components/supervised_user/core/browser/supervised_user_utils.h" #include "components/sync/protocol/sync_enums.pb.h" #include "components/sync/service/sync_service.h" #include "components/sync_device_info/device_info.h" #include "components/sync_device_info/device_info_sync_service.h" #include "components/sync_device_info/device_info_tracker.h" #include "content/public/browser/url_data_source.h" #include "content/public/browser/web_ui.h" #include "third_party/abseil-cpp/absl/container/flat_hash_map.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/time_format.h" #include "ui/webui/resources/cr_components/history/history.mojom.h" using bookmarks::BookmarkModel; using history::BrowsingHistoryService; using history::HistoryService; using history::WebHistoryService; namespace { #if !BUILDFLAG(IS_CHROMEOS) constexpr int kHistorySyncPromoShownThreshold = 5; constexpr base::TimeDelta kHistorySyncPromoCooldown = base::Days(7); history::mojom::AccountInfoPtr CreateAccountInfoDataMojo( const AccountInfo& info) { auto account_info_mojo = history::mojom::AccountInfo::New(); account_info_mojo->name = std::string(info.GetFullName().value_or("")); account_info_mojo->email = std::string(info.GetEmail()); account_info_mojo->account_image_src = GURL(signin::GetAccountPictureUrl(info)); return account_info_mojo; } #endif // Identifiers for the type of device from which a history entry originated. static const char kDeviceTypeLaptop[] = "laptop"; static const char kDeviceTypePhone[] = "phone"; static const char kDeviceTypeTablet[] = "tablet"; // Gets the name and type of a device for the given sync client ID. // |name| and |type| are out parameters. void GetDeviceNameAndType(const syncer::DeviceInfoTracker* tracker, const std::string& client_id, std::string* name, std::string* type) { // DeviceInfoTracker must be syncing in order for remote history entries to // be available. DCHECK(tracker); DCHECK(tracker->IsSyncing()); const syncer::DeviceInfo* device_info = tracker->GetDeviceInfo(client_id); if (device_info) { *name = device_info->client_name(); switch (device_info->form_factor()) { case syncer::DeviceInfo::FormFactor::kPhone: *type = kDeviceTypePhone; break; case syncer::DeviceInfo::FormFactor::kTablet: *type = kDeviceTypeTablet; break; // return the laptop icon as default. case syncer::DeviceInfo::FormFactor::kUnknown: [[fallthrough]]; case syncer::DeviceInfo::FormFactor::kAutomotive: [[fallthrough]]; case syncer::DeviceInfo::FormFactor::kWearable: [[fallthrough]]; case syncer::DeviceInfo::FormFactor::kTv: [[fallthrough]]; case syncer::DeviceInfo::FormFactor::kDesktop: *type = kDeviceTypeLaptop; } return; } *name = l10n_util::GetStringUTF8(IDS_HISTORY_UNKNOWN_DEVICE); *type = kDeviceTypeLaptop; } // Formats `entry`'s URL and title and adds them to `result`. std::pair SetHistoryEntryUrlAndTitle( const BrowsingHistoryService::HistoryEntry& entry) { bool using_url_as_the_title = false; std::u16string title_to_set(entry.title); if (entry.title.empty()) { using_url_as_the_title = true; title_to_set = base::UTF8ToUTF16(entry.url.spec()); } // Since the title can contain BiDi text, we need to mark the text as either // RTL or LTR, depending on the characters in the string. If we use the URL // as the title, we mark the title as LTR since URLs are always treated as // left to right strings. if (base::i18n::IsRTL()) { if (using_url_as_the_title) { base::i18n::WrapStringWithLTRFormatting(&title_to_set); } else { base::i18n::AdjustStringForLocaleDirection(&title_to_set); } } // Number of chars to truncate titles when making them "short". static const size_t kShortTitleLength = 300; if (title_to_set.size() > kShortTitleLength) { title_to_set.resize(kShortTitleLength); } return std::make_tuple(entry.url.spec(), base::UTF16ToUTF8(title_to_set)); } // Helper function to check if entry is present in local database (local-side // history). bool IsUrlInLocalDatabase(const BrowsingHistoryService::HistoryEntry& entry) { switch (entry.entry_type) { case BrowsingHistoryService::HistoryEntry::EntryType::EMPTY_ENTRY: case BrowsingHistoryService::HistoryEntry::EntryType::REMOTE_ENTRY: return false; case BrowsingHistoryService::HistoryEntry::EntryType::LOCAL_ENTRY: case BrowsingHistoryService::HistoryEntry::EntryType::COMBINED_ENTRY: return true; } NOTREACHED(); } // Helper function to check if entry is present in user remote data (server-side // history). bool IsEntryInRemoteUserData( const BrowsingHistoryService::HistoryEntry& entry) { switch (entry.entry_type) { case BrowsingHistoryService::HistoryEntry::EntryType::EMPTY_ENTRY: case BrowsingHistoryService::HistoryEntry::EntryType::LOCAL_ENTRY: return false; case BrowsingHistoryService::HistoryEntry::EntryType::REMOTE_ENTRY: case BrowsingHistoryService::HistoryEntry::EntryType::COMBINED_ENTRY: return true; } NOTREACHED(); } // Expected URL types for `UrlIdentity::CreateFromUrl()`. constexpr UrlIdentity::TypeSet allowed_types = { UrlIdentity::Type::kDefault, UrlIdentity::Type::kFile, UrlIdentity::Type::kIsolatedWebApp, UrlIdentity::Type::kChromeExtension}; constexpr UrlIdentity::FormatOptions url_identity_options{ .default_options = {UrlIdentity::DefaultFormatOptions:: kOmitSchemePathAndTrivialSubdomains}}; history::mojom::FilteringBehavior FilteringBehaviorToMojom( supervised_user::FilteringBehavior filtering_behavior) { switch (filtering_behavior) { case supervised_user::FilteringBehavior::kAllow: return history::mojom::FilteringBehavior::kAllow; case supervised_user::FilteringBehavior::kBlock: return history::mojom::FilteringBehavior::kBlock; case supervised_user::FilteringBehavior::kInvalid: return history::mojom::FilteringBehavior::kInvalid; default: return history::mojom::FilteringBehavior::kUnknown; } } // Converts `entry` to a history::mojom::QueryResult to be owned by the caller. history::mojom::HistoryEntryPtr HistoryEntryToMojom( const BrowsingHistoryService::HistoryEntry& entry, BookmarkModel* bookmark_model, Profile& profile, const syncer::DeviceInfoTracker* tracker, base::Clock* clock) { auto result_mojom = history::mojom::HistoryEntry::New(); base::DictValue dictionary; auto url_and_title = SetHistoryEntryUrlAndTitle(entry); result_mojom->url = url_and_title.first; result_mojom->title = url_and_title.second; // UrlIdentity holds a user-identifiable string for a URL. We will display // this string to the user. std::u16string domain = UrlIdentity::CreateFromUrl(&profile, entry.url, allowed_types, url_identity_options) .name; // When the domain is empty, use the scheme instead. This allows for a // sensible treatment of e.g. file: URLs when group by domain is on. if (domain.empty()) { domain = base::UTF8ToUTF16(entry.url.GetScheme() + ":"); } // The items which are to be written into result are also described in // chrome/browser/resources/history/history.js in @typedef for // HistoryEntry. Please update it whenever you add or remove // any keys in result. result_mojom->domain = base::UTF16ToUTF8(domain); result_mojom->fallback_favicon_text = base::UTF16ToUTF8(favicon::GetFallbackIconText(entry.url)); result_mojom->time = entry.time.InMillisecondsFSinceUnixEpoch(); // Pass the timestamps in a map. base::flat_map> all_timestamps; for (const auto& [url, timestamps] : entry.all_timestamps) { std::vector timestamps_for_url; // Add all timestamps for this URL. for (const base::Time& timestamp : timestamps) { timestamps_for_url.push_back(timestamp.InMillisecondsFSinceUnixEpoch()); } all_timestamps[url.spec()] = std::move(timestamps_for_url); } result_mojom->all_timestamps = std::move(all_timestamps); // Always pass the short date since it is needed both in the search and in // the monthly view. result_mojom->date_short = base::UTF16ToUTF8(base::TimeFormatShortDate(entry.time)); std::u16string snippet_string; std::u16string date_relative_day; std::u16string date_time_of_day; bool is_blocked_visit = false; // Only pass in the strings we need (search results need a shortdate // and snippet, browse results need day and time information). Makes sure that // values of result are never undefined if (entry.is_search_result) { snippet_string = entry.snippet; } else { std::u16string date_str = ui::TimeFormat::RelativeDate(entry.time, clock->Now().LocalMidnight()); if (date_str.empty()) { date_str = base::TimeFormatFriendlyDate(entry.time); } else { date_str = l10n_util::GetStringFUTF16( IDS_HISTORY_DATE_WITH_RELATIVE_TIME, date_str, base::TimeFormatFriendlyDate(entry.time)); } date_relative_day = date_str; date_time_of_day = base::TimeFormatTimeOfDay(entry.time); } std::string device_name; std::string device_type; if (!entry.client_id.empty()) { GetDeviceNameAndType(tracker, entry.client_id, &device_name, &device_type); } result_mojom->device_name = device_name; result_mojom->device_type = device_type; supervised_user::FilteringBehavior filtering_behavior; if (profile.IsChild()) { filtering_behavior = supervised_user::SupervisedUserUrlFilteringServiceFactory:: GetForProfile(&profile) ->GetFilteringBehavior(entry.url.GetWithEmptyPath()) .behavior; is_blocked_visit = entry.blocked_visit; result_mojom->host_filtering_behavior = FilteringBehaviorToMojom(filtering_behavior); } else { result_mojom->host_filtering_behavior = history::mojom::FilteringBehavior::kUnknown; } result_mojom->date_time_of_day = base::UTF16ToUTF8(date_time_of_day); result_mojom->date_relative_day = base::UTF16ToUTF8(date_relative_day); result_mojom->snippet = base::UTF16ToUTF8(snippet_string); result_mojom->starred = bookmark_model->IsBookmarked(entry.url); result_mojom->blocked_visit = is_blocked_visit; result_mojom->is_url_in_remote_user_data = IsEntryInRemoteUserData(entry); result_mojom->remote_icon_url_for_uma = entry.remote_icon_url_for_uma.spec(); result_mojom->is_actor_visit = entry.is_actor_visit; // Additional debugging fields shown only if the debug feature is enabled. if (history_clusters::GetConfig().user_visible_debug) { auto debug_mojom = history::mojom::DebugInfo::New(); debug_mojom->is_url_in_local_database = IsUrlInLocalDatabase(entry); debug_mojom->visit_count = entry.visit_count; debug_mojom->typed_count = entry.typed_count; result_mojom->debug = std::move(debug_mojom); } // Initialize critical_actions array. result_mojom->critical_actions = std::vector(); return result_mojom; } history::mojom::CriticalActionPtr CriticalActionToMojom( const critical_actions::CriticalActionEntry& action) { auto action_mojom = history::mojom::CriticalAction::New(); action_mojom->id = action.critical_action_id; action_mojom->linkout_url = critical_actions::GetCriticalActionLinkoutUrl(action); action_mojom->label = action.GetLabel(); action_mojom->tooltip = action.GetTooltip(); action_mojom->action_type = static_cast(action.action_type); return action_mojom; } struct ActionGroupKey { std::string_view conversation_id; std::string_view actor_task_id; int64_t visit_id; critical_actions::ActionType action_type; auto operator<=>(const ActionGroupKey&) const = default; }; std::vector DeduplicateCriticalActions( const std::vector& raw_actions, base::TimeDelta time_tolerance) { if (raw_actions.empty()) { return {}; } base::flat_map group_latest_times; std::vector deduped_results; deduped_results.reserve(raw_actions.size()); for (const auto& action : raw_actions) { ActionGroupKey key{.conversation_id = action.conversation_id, .actor_task_id = action.actor_task_id, .visit_id = action.visit_id, .action_type = action.action_type}; auto it = group_latest_times.find(key); // The database returns actions sorted by timestamp DESC (latest first). // A subsequent action falls into the tolerance window of the previous // action if its timestamp is greater than or equal to the // (last_accepted_timestamp - time_tolerance). if (it != group_latest_times.end() && action.timestamp >= (it->second - time_tolerance)) { continue; // It's a duplicate, skip it. } group_latest_times[key] = action.timestamp; deduped_results.push_back(action); } return deduped_results; } } // namespace BrowsingHistoryHandler::BrowsingHistoryHandler( mojo::PendingReceiver pending_page_handler, Profile* profile, content::WebContents* web_contents) : profile_(profile), web_contents_(web_contents), page_handler_(this, std::move(pending_page_handler)), identity_manager_( CHECK_DEREF(IdentityManagerFactory::GetForProfile(profile))), clock_(base::DefaultClock::GetInstance()), browsing_history_service_(nullptr) {} BrowsingHistoryHandler::~BrowsingHistoryHandler() = default; void BrowsingHistoryHandler::SetSidePanelUIEmbedder( base::WeakPtr side_panel_embedder) { side_panel_embedder_ = side_panel_embedder; } void BrowsingHistoryHandler::SetPage( mojo::PendingRemote pending_page) { page_.Bind(std::move(pending_page)); // TODO(mfacey@): Explore whether deferred_callbacks_ can be removed. for (auto& deferred_callback : deferred_callbacks_) { std::move(deferred_callback).Run(); } deferred_callbacks_.clear(); HatsService* hats_service = HatsServiceFactory::GetForProfile(profile_, /* create_if_necessary = */ true); if (!hats_service) { return; } // Experiment group HaTS survey should trigger if the HaTS experiment group // feature is enabled and any of the history improvement features is enabled. // Check the HaTS feature last, so that clients are only counted as active if // one of the other feature is enabled. if ((history::IsBrowsingHistoryActorIntegrationM3Enabled() || base::FeatureList::IsEnabled( history::kBrowsingHistorySimilarVisitsGrouping)) && base::FeatureList::IsEnabled( features::kHappinessTrackingSurveysForDesktopHistoryPageExperiment)) { hats_service->LaunchDelayedSurveyForWebContents( kHatsSurveyTriggerHistoryPageExperiment, web_contents_, features::kHappinessTrackingSurveysForDesktopHistoryPageExperimentTime .Get() .InMilliseconds()); } // Control group HaTS survey should trigger if the HaTS control group feature // is enabled and none of the history improvement features is enabled. // Check the HaTS feature last, so that clients are only counted as active if // all of the other features are disabled. if (!history::IsBrowsingHistoryActorIntegrationM3Enabled() && !base::FeatureList::IsEnabled( history::kBrowsingHistorySimilarVisitsGrouping) && base::FeatureList::IsEnabled( features::kHappinessTrackingSurveysForDesktopHistoryPageControl)) { hats_service->LaunchDelayedSurveyForWebContents( kHatsSurveyTriggerHistoryPageControl, web_contents_, features::kHappinessTrackingSurveysForDesktopHistoryPageControlTime .Get() .InMilliseconds()); } } void BrowsingHistoryHandler::ShowSidePanelUI() { if (side_panel_embedder_) { side_panel_embedder_->ShowUI(); } } void BrowsingHistoryHandler::StartQueryHistory() { HistoryService* local_history = HistoryServiceFactory::GetForProfile( profile_, ServiceAccessType::EXPLICIT_ACCESS); syncer::SyncService* sync_service = SyncServiceFactory::GetForProfile(profile_); browsing_history_service_ = std::make_unique( this, local_history, sync_service); // 150 = RESULTS_PER_PAGE from chrome/browser/resources/history/constants.js SendHistoryQuery(150, std::string(), std::nullopt, true, true); } void BrowsingHistoryHandler::QueryHistory(const std::string& query, int max_count, std::optional begin_timestamp, bool include_user_visits, bool include_actor_visits, QueryHistoryCallback callback) { if (!browsing_history_service_) { // Page was refreshed, so need to call StartQueryHistory here StartQueryHistory(); } // Reset the query history continuation callback. Since it is repopulated in // OnQueryComplete(), it cannot be reset earlier, as the early return above // prevents the QueryHistory() call to the browsing history service. query_history_continuation_.Reset(); // Cancel the previous query if it is still in flight. if (query_history_callback_) { std::move(query_history_callback_).Run(history::mojom::QueryResult::New()); } query_history_callback_ = std::move(callback); SendHistoryQuery(max_count, query, begin_timestamp, include_user_visits, include_actor_visits); } void BrowsingHistoryHandler::SendHistoryQuery( int max_count, const std::string& query, std::optional begin_timestamp, bool include_user_visits, bool include_actor_visits) { query_timer_ = base::ElapsedTimer(); history::QueryOptions options; options.max_count = max_count; options.policy_for_404_visits = history::VisitQuery404sPolicy::kExclude404s; options.duplicate_policy = history::QueryOptions::REMOVE_DUPLICATES_PER_DAY; options.include_actor_visits = include_actor_visits; options.include_user_visits = include_user_visits; std::string query_without_prefix = query; const std::string kHostPrefix = "host:"; if (query.rfind(kHostPrefix, 0) == 0) { options.host_only = true; query_without_prefix = query.substr(kHostPrefix.length()); } if (begin_timestamp.has_value()) { options.begin_time = base::Time::FromMillisecondsSinceUnixEpoch(begin_timestamp.value()); } browsing_history_service_->QueryHistory( base::UTF8ToUTF16(query_without_prefix), options); } void BrowsingHistoryHandler::QueryHistoryContinuation( QueryHistoryContinuationCallback callback) { // Cancel the previous query if it is still in flight. if (query_history_callback_) { std::move(query_history_callback_).Run(history::mojom::QueryResult::New()); } query_history_callback_ = std::move(callback); if (!query_history_continuation_.is_null()) { std::move(query_history_continuation_).Run(); } } void BrowsingHistoryHandler::RemoveVisits( const std::vector items, RemoveVisitsCallback callback) { remove_visits_callbacks_.push(std::move(callback)); std::vector items_to_remove; items_to_remove.reserve(items.size()); for (const auto& item : items) { const std::string url = item->url; const std::vector timestamps = item->timestamps; if (url.empty()) { NOTREACHED() << "Unable to extract arguments"; } DCHECK_GT(timestamps.size(), 0U); BrowsingHistoryService::HistoryEntry entry; entry.url = GURL(url); for (const auto& timestamp : timestamps) { base::Time visit_time = base::Time::FromMillisecondsSinceUnixEpoch(timestamp); entry.all_timestamps[entry.url].insert(visit_time); } items_to_remove.push_back(entry); } browsing_history_service_->RemoveVisits(items_to_remove); } void BrowsingHistoryHandler::OpenClearBrowsingDataDialog() { // TODO(beng): This is an improper direct dependency on Browser. Route this // through some sort of delegate. BrowserWindowInterface* browser = GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_); chrome::ShowClearBrowsingDataDialog(browser); } void BrowsingHistoryHandler::TurnOnHistorySync() { #if !BUILDFLAG(IS_CHROMEOS) BrowserWindowInterface* browser = GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(web_contents_); if (browser) { signin_ui_util::SignInAndEnableHistorySync( browser, profile_, signin_metrics::AccessPoint::kRecentTabs); } #else // This is not expected to be called on ChromeOS as the screen that uses this // function is never shown for ChromeOS (using ). NOTREACHED(); #endif } #if !BUILDFLAG(IS_CHROMEOS) void BrowsingHistoryHandler::ShouldShowHistoryPageHistorySyncPromo( ShouldShowHistoryPageHistorySyncPromoCallback callback) { const int promo_shown_count = GetHistoryPageHistorySyncPromoShownCount(); // If the promo has been shown more than the threshold, the promo should not // be shown. if (promo_shown_count >= kHistorySyncPromoShownThreshold) { std::move(callback).Run(false); return; } const bool shown_after_dismissal = IsHistoryPageHistorySyncPromoShownAfterDismissal(); // If the promo was dismissed and shown once after dismissal, the promo should // not be shown anymore. if (shown_after_dismissal) { std::move(callback).Run(false); return; } const base::Time last_dismissed_timestamp = GetHistoryPageHistorySyncPromoLastDismissedTimestamp(); const bool was_dismissed = !last_dismissed_timestamp.is_null(); // If the promo was dismissed and the cooldown has not passed, the promo // should not be shown. if (was_dismissed && clock_->Now() < last_dismissed_timestamp + kHistorySyncPromoCooldown) { std::move(callback).Run(false); return; } std::move(callback).Run(true); } void BrowsingHistoryHandler::RecordHistoryPageHistorySyncPromoDismissed() { SetHistoryPageHistorySyncPromoLastDismissedTimestamp(clock_->Now()); } void BrowsingHistoryHandler::IncrementHistoryPageHistorySyncPromoShownCount() { IncrementHistoryPageHistorySyncPromoShownCountPref(); const base::Time last_dismissed_timestamp = GetHistoryPageHistorySyncPromoLastDismissedTimestamp(); const bool was_dismissed = !last_dismissed_timestamp.is_null(); if (was_dismissed) { SetHistoryPageHistorySyncPromoShownAfterDismissal(); } } int BrowsingHistoryHandler::GetHistoryPageHistorySyncPromoShownCount() const { const AccountInfo account = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (account.GetGaiaId().empty()) { return profile_->GetPrefs()->GetInteger( prefs::kHistoryPageHistorySyncPromoShownCountPerProfile); } return SigninPrefs(*profile_->GetPrefs()) .GetHistoryPageHistorySyncPromoShownCount(account.GetGaiaId()); } base::Time BrowsingHistoryHandler::GetHistoryPageHistorySyncPromoLastDismissedTimestamp() const { const AccountInfo account = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (account.GetGaiaId().empty()) { return profile_->GetPrefs()->GetTime( prefs::kHistoryPageHistorySyncPromoLastDismissedTimestampPerProfile); } return SigninPrefs(*profile_->GetPrefs()) .GetHistoryPageHistorySyncPromoLastDismissedTimestamp(account.GetGaiaId()) .value_or(base::Time()); } bool BrowsingHistoryHandler::IsHistoryPageHistorySyncPromoShownAfterDismissal() const { const AccountInfo account = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (account.GetGaiaId().empty()) { return profile_->GetPrefs()->GetBoolean( prefs::kHistoryPageHistorySyncPromoShownAfterDismissalPerProfile); } return SigninPrefs(*profile_->GetPrefs()) .GetHistoryPageHistorySyncPromoShownAfterDismissal(account.GetGaiaId()); } void BrowsingHistoryHandler:: SetHistoryPageHistorySyncPromoLastDismissedTimestamp(base::Time time) { const AccountInfo account = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (account.GetGaiaId().empty()) { profile_->GetPrefs()->SetTime( prefs::kHistoryPageHistorySyncPromoLastDismissedTimestampPerProfile, time); } else { SigninPrefs(*profile_->GetPrefs()) .SetHistoryPageHistorySyncPromoLastDismissedTimestamp( account.GetGaiaId(), time); } } void BrowsingHistoryHandler:: IncrementHistoryPageHistorySyncPromoShownCountPref() { const AccountInfo account = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (account.GetGaiaId().empty()) { const int promo_shown_count = profile_->GetPrefs()->GetInteger( prefs::kHistoryPageHistorySyncPromoShownCountPerProfile); profile_->GetPrefs()->SetInteger( prefs::kHistoryPageHistorySyncPromoShownCountPerProfile, promo_shown_count + 1); } else { SigninPrefs(*profile_->GetPrefs()) .IncrementHistoryPageHistorySyncPromoShownCount(account.GetGaiaId()); } } void BrowsingHistoryHandler:: SetHistoryPageHistorySyncPromoShownAfterDismissal() { const AccountInfo account = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (account.GetGaiaId().empty()) { profile_->GetPrefs()->SetBoolean( prefs::kHistoryPageHistorySyncPromoShownAfterDismissalPerProfile, true); } else { SigninPrefs(*profile_->GetPrefs()) .SetHistoryPageHistorySyncPromoShownAfterDismissal(account.GetGaiaId()); } } #endif void BrowsingHistoryHandler::RemoveBookmark(const std::string& url) { BookmarkModel* model = BookmarkModelFactory::GetForBrowserContext(profile_); bookmarks::RemoveAllBookmarks(model, GURL(url), FROM_HERE); } // void BrowsingHistoryHandler::SetLastSelectedTab(const int last_tab) { profile_->GetPrefs()->SetInteger(history_clusters::prefs::kLastSelectedTab, last_tab); } void BrowsingHistoryHandler::OnQueryComplete( const std::vector& results, const BrowsingHistoryService::QueryResultsInfo& query_results_info, base::OnceClosure continuation_closure) { query_history_continuation_ = std::move(continuation_closure); CHECK(profile_); if (base::FeatureList::IsEnabled( critical_actions::features::kCriticalActionHistory)) { std::vector actor_visit_ids; for (const auto& entry : results) { if (entry.is_actor_visit) { actor_visit_ids.insert(actor_visit_ids.end(), entry.all_visit_ids.begin(), entry.all_visit_ids.end()); } } if (!actor_visit_ids.empty()) { critical_actions::CriticalActionService* critical_action_service = critical_actions::CriticalActionFactory::GetForProfile(profile_); if (critical_action_service) { critical_actions::CriticalActionQueryOptions options; options.visit_ids = std::move(actor_visit_ids); critical_action_service->GetCriticalActions( options, base::BindOnce(&BrowsingHistoryHandler::CriticalActionsFetched, weak_factory_.GetWeakPtr(), results, query_results_info, base::ElapsedTimer())); return; } } } HandleQueryResults(results, query_results_info, {}); } void BrowsingHistoryHandler::CriticalActionsFetched( const std::vector& results, const BrowsingHistoryService::QueryResultsInfo& query_results_info, base::ElapsedTimer critical_actions_timer, std::vector critical_actions) { base::UmaHistogramTimes("HistoryPage.CriticalActionsQueryTime", critical_actions_timer.Elapsed()); HandleQueryResults(results, query_results_info, std::move(critical_actions)); } void BrowsingHistoryHandler::HandleQueryResults( const std::vector& results, const BrowsingHistoryService::QueryResultsInfo& query_results_info, std::vector critical_actions) { if (query_timer_.has_value()) { const bool has_actor_visits = std::any_of(results.begin(), results.end(), [](const auto& entry) { return entry.is_actor_visit; }); if (has_actor_visits && base::FeatureList::IsEnabled( critical_actions::features::kCriticalActionHistory)) { base::UmaHistogramTimes( "HistoryPage.QueryHistoryTotalTime.WithCriticalActions", query_timer_->Elapsed()); } else { base::UmaHistogramTimes( "HistoryPage.QueryHistoryTotalTime.WithoutCriticalActions", query_timer_->Elapsed()); } query_timer_.reset(); } BookmarkModel* bookmark_model = BookmarkModelFactory::GetForBrowserContext(profile_); const syncer::DeviceInfoTracker* tracker = DeviceInfoSyncServiceFactory::GetForProfile(profile_) ->GetDeviceInfoTracker(); DCHECK(tracker); absl::flat_hash_map> actions_by_visit_id; // Deduplicate actions belonging to the same task and visit. // 5 seconds is chosen as a safe heuristic upper bound to accommodate // potential latency delays between the Actor and Chrome side logs // of the same event, while being small enough to avoid merging separate // events. std::vector processed_actions = DeduplicateCriticalActions(critical_actions, base::Seconds(5)); for (const auto& action : processed_actions) { if (action.visit_id == history::kInvalidVisitID || action.action_type == critical_actions::ActionType::kUnknown) { continue; } actions_by_visit_id[action.visit_id].push_back( CriticalActionToMojom(action)); } std::vector results_mojom; for (const BrowsingHistoryService::HistoryEntry& entry : results) { history::mojom::HistoryEntryPtr entry_mojom = HistoryEntryToMojom(entry, bookmark_model, *profile_, tracker, clock_); if (entry.is_actor_visit && base::FeatureList::IsEnabled( critical_actions::features::kCriticalActionHistory)) { for (history::VisitID visit_id : entry.all_visit_ids) { auto it = actions_by_visit_id.find(visit_id); if (it != actions_by_visit_id.end()) { for (auto& action : it->second) { entry_mojom->critical_actions.push_back(std::move(action)); } } } base::UmaHistogramCounts100("HistoryPage.CriticalActionsPerVisitCount", entry_mojom->critical_actions.size()); } results_mojom.push_back(std::move(entry_mojom)); } auto results_info = history::mojom::HistoryQuery::New(); // The items which are to be written into results_info_ are also // described in ui/webui/resources/cr_components/history/history.mojom. results_info->term = base::UTF16ToUTF8(query_results_info.search_text); results_info->finished = query_results_info.reached_beginning; auto final_results = history::mojom::QueryResult::New(); final_results->info = std::move(results_info); final_results->value = std::move(results_mojom); if (query_history_callback_) { std::move(query_history_callback_).Run(std::move(final_results)); } } void BrowsingHistoryHandler::OnRemoveVisitsComplete() { CHECK(!remove_visits_callbacks_.empty()); std::move(remove_visits_callbacks_.front()).Run(); remove_visits_callbacks_.pop(); } void BrowsingHistoryHandler::OnRemoveVisitsFailed() { CHECK(!remove_visits_callbacks_.empty()); std::move(remove_visits_callbacks_.front()).Run(); remove_visits_callbacks_.pop(); } void BrowsingHistoryHandler::HistoryDeleted() { if (page_) { page_->OnHistoryDeleted(); } else { deferred_callbacks_.push_back(base::BindOnce( &BrowsingHistoryHandler::HistoryDeleted, weak_factory_.GetWeakPtr())); } } void BrowsingHistoryHandler::HasOtherFormsOfBrowsingHistory( bool has_other_forms, bool has_synced_results) { if (page_) { page_->OnHasOtherFormsChanged(has_other_forms); } else { deferred_callbacks_.push_back(base::BindOnce( &BrowsingHistoryHandler::HasOtherFormsOfBrowsingHistory, weak_factory_.GetWeakPtr(), has_other_forms, has_synced_results)); } } Profile* BrowsingHistoryHandler::GetProfile() { return profile_; } void BrowsingHistoryHandler::RequestAccountInfo( RequestAccountInfoCallback callback) { #if !BUILDFLAG(IS_CHROMEOS) AccountInfo account_info = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); std::move(callback).Run(CreateAccountInfoDataMojo(account_info)); if (!identity_manager_observation_.IsObserving()) { identity_manager_observation_.Observe(&identity_manager_.get()); } #else // This is not expected to be called on ChromeOS as the screen that uses this // function is never shown for ChromeOS (using ). NOTREACHED(); #endif } void BrowsingHistoryHandler::OnExtendedAccountInfoUpdated( const AccountInfo& info) { #if !BUILDFLAG(IS_CHROMEOS) AccountInfo account_to_display = signin_ui_util::GetSingleAccountForPromos( &identity_manager_.get(), AccountPreviewDataServiceFactory::GetForProfile(profile_)); if (info.IsEmpty() || !info.IsValid() || info.GetAccountId() != account_to_display.GetAccountId()) { return; } page_->SendAccountInfo(CreateAccountInfoDataMojo(info)); #else // This is not expected to be called on ChromeOS as the screen that uses this // function is never shown for ChromeOS (using ). NOTREACHED(); #endif }