123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- // Copyright 2022 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/browsing_topics/browsing_topics_service_impl.h"
- #include <random>
- #include "base/rand_util.h"
- #include "base/ranges/algorithm.h"
- #include "base/time/time.h"
- #include "components/browsing_topics/browsing_topics_calculator.h"
- #include "components/browsing_topics/browsing_topics_page_load_data_tracker.h"
- #include "components/browsing_topics/mojom/browsing_topics_internals.mojom.h"
- #include "components/browsing_topics/util.h"
- #include "components/optimization_guide/content/browser/page_content_annotations_service.h"
- #include "content/public/browser/browsing_topics_site_data_manager.h"
- #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
- #include "services/metrics/public/cpp/ukm_builders.h"
- #include "services/metrics/public/cpp/ukm_recorder.h"
- #include "third_party/blink/public/common/features.h"
- #include "third_party/blink/public/mojom/browsing_topics/browsing_topics.mojom.h"
- namespace browsing_topics {
- namespace {
- // Returns whether the topics should all be cleared given
- // `browsing_topics_data_accessible_since` and `is_topic_allowed_by_settings`.
- // Returns true if `browsing_topics_data_accessible_since` is greater than the
- // last calculation time, or if any top topic is disallowed from the settings.
- // The latter could happen if the topic became disallowed when
- // `browsing_topics_state` was still loading (and we didn't get a chance to
- // clear it). This is an unlikely edge case, so it's fine to over-delete.
- bool ShouldClearTopicsOnStartup(
- const BrowsingTopicsState& browsing_topics_state,
- base::Time browsing_topics_data_accessible_since,
- base::RepeatingCallback<bool(const privacy_sandbox::CanonicalTopic&)>
- is_topic_allowed_by_settings) {
- DCHECK(!is_topic_allowed_by_settings.is_null());
- if (browsing_topics_state.epochs().empty())
- return false;
- // Here we rely on the fact that `browsing_topics_data_accessible_since` can
- // only be updated to base::Time::Now() due to data deletion. So we'll either
- // need to clear all topics data, or no-op. If this assumption no longer
- // holds, we'd need to iterate over all epochs, check their calculation time,
- // and selectively delete the epochs.
- if (browsing_topics_data_accessible_since >
- browsing_topics_state.epochs().back().calculation_time()) {
- return true;
- }
- for (const EpochTopics& epoch : browsing_topics_state.epochs()) {
- for (const TopicAndDomains& topic_and_domains :
- epoch.top_topics_and_observing_domains()) {
- if (!topic_and_domains.IsValid())
- continue;
- if (!is_topic_allowed_by_settings.Run(privacy_sandbox::CanonicalTopic(
- topic_and_domains.topic(), epoch.taxonomy_version()))) {
- return true;
- }
- }
- }
- return false;
- }
- struct StartupCalculateDecision {
- bool clear_topics_data = true;
- base::TimeDelta next_calculation_delay;
- };
- StartupCalculateDecision GetStartupCalculationDecision(
- const BrowsingTopicsState& browsing_topics_state,
- base::Time browsing_topics_data_accessible_since,
- base::RepeatingCallback<bool(const privacy_sandbox::CanonicalTopic&)>
- is_topic_allowed_by_settings) {
- // The topics have never been calculated. This could happen with a fresh
- // profile or the if the config has updated. In case of a config update, the
- // topics should have already been cleared when initializing the
- // `BrowsingTopicsState`.
- if (browsing_topics_state.next_scheduled_calculation_time().is_null()) {
- return StartupCalculateDecision{
- .clear_topics_data = false,
- .next_calculation_delay = base::TimeDelta()};
- }
- // This could happen when clear-on-exit is turned on and has caused the
- // cookies to be deleted on startup, of if a topic became disallowed when
- // `browsing_topics_state` was still loading.
- bool should_clear_topics_data = ShouldClearTopicsOnStartup(
- browsing_topics_state, browsing_topics_data_accessible_since,
- is_topic_allowed_by_settings);
- base::TimeDelta presumed_next_calculation_delay =
- browsing_topics_state.next_scheduled_calculation_time() -
- base::Time::Now();
- // The scheduled calculation time was reached before the startup.
- if (presumed_next_calculation_delay <= base::TimeDelta()) {
- return StartupCalculateDecision{
- .clear_topics_data = should_clear_topics_data,
- .next_calculation_delay = base::TimeDelta()};
- }
- // This could happen if the machine time has changed since the last
- // calculation. Recalculate immediately to align with the expected schedule
- // rather than potentially stop computing for a very long time.
- if (presumed_next_calculation_delay >=
- 2 * blink::features::kBrowsingTopicsTimePeriodPerEpoch.Get()) {
- return StartupCalculateDecision{
- .clear_topics_data = should_clear_topics_data,
- .next_calculation_delay = base::TimeDelta()};
- }
- return StartupCalculateDecision{
- .clear_topics_data = should_clear_topics_data,
- .next_calculation_delay = presumed_next_calculation_delay};
- }
- // Represents the different reasons why the topics API returns an empty result.
- // These values are persisted to logs. Entries should not be renumbered and
- // numeric values should never be reused.
- enum class EmptyApiResultReason {
- // The topics state hasn't finished loading.
- kStateNotReady = 0,
- // Access is disallowed by user settings.
- kAccessDisallowedBySettings = 1,
- // There are no candidate topics, e.g. no candidate epochs; epoch calculation
- // failed; individual topics were cleared or blocked.
- kNoCandicateTopics = 2,
- // The candidate topics were filtered for the requesting context.
- kCandicateTopicsFiltered = 3,
- kMaxValue = kCandicateTopicsFiltered,
- };
- void RecordBrowsingTopicsApiResultUkmMetrics(
- EmptyApiResultReason empty_reason,
- content::RenderFrameHost* main_frame) {
- ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
- ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult builder(
- main_frame->GetPageUkmSourceId());
- builder.SetEmptyReason(static_cast<int64_t>(empty_reason));
- builder.Record(ukm_recorder->Get());
- }
- void RecordBrowsingTopicsApiResultUkmMetrics(
- const std::vector<std::pair<blink::mojom::EpochTopicPtr, bool>>&
- topics_with_status,
- content::RenderFrameHost* main_frame) {
- DCHECK(!topics_with_status.empty());
- ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
- ukm::builders::BrowsingTopics_DocumentBrowsingTopicsApiResult builder(
- main_frame->GetPageUkmSourceId());
- for (size_t i = 0; i < 3u && topics_with_status.size() > i; ++i) {
- const blink::mojom::EpochTopicPtr& topic = topics_with_status[i].first;
- bool is_true_topic = topics_with_status[i].second;
- int taxonomy_version = 0;
- base::StringToInt(topic->taxonomy_version, &taxonomy_version);
- DCHECK(taxonomy_version);
- int64_t model_version = 0;
- base::StringToInt64(topic->model_version, &model_version);
- DCHECK(model_version);
- if (i == 0) {
- builder.SetReturnedTopic0(topic->topic)
- .SetReturnedTopic0IsTrueTopTopic(is_true_topic)
- .SetReturnedTopic0TaxonomyVersion(taxonomy_version)
- .SetReturnedTopic0ModelVersion(model_version);
- } else if (i == 1) {
- builder.SetReturnedTopic1(topic->topic)
- .SetReturnedTopic1IsTrueTopTopic(is_true_topic)
- .SetReturnedTopic1TaxonomyVersion(taxonomy_version)
- .SetReturnedTopic1ModelVersion(model_version);
- } else {
- DCHECK_EQ(i, 2u);
- builder.SetReturnedTopic2(topic->topic)
- .SetReturnedTopic2IsTrueTopTopic(is_true_topic)
- .SetReturnedTopic2TaxonomyVersion(taxonomy_version)
- .SetReturnedTopic2ModelVersion(model_version);
- }
- }
- builder.Record(ukm_recorder->Get());
- }
- } // namespace
- BrowsingTopicsServiceImpl::~BrowsingTopicsServiceImpl() = default;
- BrowsingTopicsServiceImpl::BrowsingTopicsServiceImpl(
- const base::FilePath& profile_path,
- privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
- history::HistoryService* history_service,
- content::BrowsingTopicsSiteDataManager* site_data_manager,
- optimization_guide::PageContentAnnotationsService* annotations_service)
- : privacy_sandbox_settings_(privacy_sandbox_settings),
- history_service_(history_service),
- site_data_manager_(site_data_manager),
- annotations_service_(annotations_service),
- browsing_topics_state_(
- profile_path,
- base::BindOnce(
- &BrowsingTopicsServiceImpl::OnBrowsingTopicsStateLoaded,
- base::Unretained(this))) {
- privacy_sandbox_settings_observation_.Observe(privacy_sandbox_settings);
- history_service_observation_.Observe(history_service);
- // Greedily request the model to be available to reduce the latency in later
- // topics calculation.
- annotations_service_->RequestAndNotifyWhenModelAvailable(
- optimization_guide::AnnotationType::kPageTopics, base::DoNothing());
- }
- std::vector<blink::mojom::EpochTopicPtr>
- BrowsingTopicsServiceImpl::GetBrowsingTopicsForJsApi(
- const url::Origin& context_origin,
- content::RenderFrameHost* main_frame) {
- if (!browsing_topics_state_loaded_) {
- RecordBrowsingTopicsApiResultUkmMetrics(
- EmptyApiResultReason::kStateNotReady, main_frame);
- return {};
- }
- if (!privacy_sandbox_settings_->IsTopicsAllowed()) {
- RecordBrowsingTopicsApiResultUkmMetrics(
- EmptyApiResultReason::kAccessDisallowedBySettings, main_frame);
- return {};
- }
- if (!privacy_sandbox_settings_->IsTopicsAllowedForContext(
- context_origin.GetURL(), main_frame->GetLastCommittedOrigin())) {
- RecordBrowsingTopicsApiResultUkmMetrics(
- EmptyApiResultReason::kAccessDisallowedBySettings, main_frame);
- return {};
- }
- std::string context_domain =
- net::registry_controlled_domains::GetDomainAndRegistry(
- context_origin.GetURL(),
- net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
- HashedDomain hashed_context_domain = HashContextDomainForStorage(
- browsing_topics_state_.hmac_key(), context_domain);
- // Track the API usage context after the permissions check.
- BrowsingTopicsPageLoadDataTracker::GetOrCreateForPage(main_frame->GetPage())
- ->OnBrowsingTopicsApiUsed(hashed_context_domain, history_service_);
- std::string top_domain =
- net::registry_controlled_domains::GetDomainAndRegistry(
- main_frame->GetLastCommittedOrigin().GetURL(),
- net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
- bool has_filtered_topics = false;
- // The result topics along with flags denoting whether they are true topics.
- std::vector<std::pair<blink::mojom::EpochTopicPtr, bool>> topics_with_status;
- for (const EpochTopics* epoch :
- browsing_topics_state_.EpochsForSite(top_domain)) {
- bool output_is_true_topic = false;
- bool candidate_topic_filtered = false;
- absl::optional<Topic> topic = epoch->TopicForSite(
- top_domain, hashed_context_domain, browsing_topics_state_.hmac_key(),
- output_is_true_topic, candidate_topic_filtered);
- if (candidate_topic_filtered)
- has_filtered_topics = true;
- // Only add a non-empty topic to the result.
- if (!topic)
- continue;
- // Although a top topic can never be in the disallowed state, the returned
- // `topic` may be the random one. Thus we still need this check.
- if (!privacy_sandbox_settings_->IsTopicAllowed(
- privacy_sandbox::CanonicalTopic(*topic,
- epoch->taxonomy_version()))) {
- continue;
- }
- auto result_topic = blink::mojom::EpochTopic::New();
- result_topic->topic = topic.value().value();
- result_topic->config_version = base::StrCat(
- {"chrome.", base::NumberToString(
- blink::features::kBrowsingTopicsConfigVersion.Get())});
- result_topic->model_version = base::NumberToString(epoch->model_version());
- result_topic->taxonomy_version =
- base::NumberToString(epoch->taxonomy_version());
- result_topic->version = base::StrCat({result_topic->config_version, ":",
- result_topic->taxonomy_version, ":",
- result_topic->model_version});
- topics_with_status.emplace_back(std::move(result_topic),
- output_is_true_topic);
- }
- // Sort `topics_with_status` based on `EpochTopicPtr` first, and if the
- // `EpochTopicPtr` parts are equal, then a true topic will be ordered before a
- // random topic. This ensures that when we later deduplicate based on the
- // `EpochTopicPtr` field only, the associated is-true-topic status will be
- // true as long as there is one true topic for that topic in
- // `topics_with_status`.
- std::sort(topics_with_status.begin(), topics_with_status.end(),
- [](const auto& left, const auto& right) {
- if (left.first < right.first)
- return true;
- if (left.first > right.first)
- return false;
- return right.second < left.second;
- });
- // Remove duplicate `EpochTopicPtr` entries.
- topics_with_status.erase(
- std::unique(topics_with_status.begin(), topics_with_status.end(),
- [](const auto& left, const auto& right) {
- return left.first == right.first;
- }),
- topics_with_status.end());
- // Shuffle the entries.
- base::RandomShuffle(topics_with_status.begin(), topics_with_status.end());
- if (topics_with_status.empty()) {
- if (has_filtered_topics) {
- RecordBrowsingTopicsApiResultUkmMetrics(
- EmptyApiResultReason::kCandicateTopicsFiltered, main_frame);
- } else {
- RecordBrowsingTopicsApiResultUkmMetrics(
- EmptyApiResultReason::kNoCandicateTopics, main_frame);
- }
- return {};
- }
- RecordBrowsingTopicsApiResultUkmMetrics(topics_with_status, main_frame);
- std::vector<blink::mojom::EpochTopicPtr> result_topics;
- result_topics.reserve(topics_with_status.size());
- std::transform(topics_with_status.begin(), topics_with_status.end(),
- std::back_inserter(result_topics),
- [](auto& topic_with_status) {
- return std::move(topic_with_status.first);
- });
- return result_topics;
- }
- void BrowsingTopicsServiceImpl::GetBrowsingTopicsStateForWebUi(
- bool calculate_now,
- mojom::PageHandler::GetBrowsingTopicsStateCallback callback) {
- if (!browsing_topics_state_loaded_) {
- std::move(callback).Run(
- mojom::WebUIGetBrowsingTopicsStateResult::NewOverrideStatusMessage(
- "State loading hasn't finished. Please retry shortly."));
- return;
- }
- // If a calculation is already in progress, get the webui topics state after
- // the calculation is done. Do this regardless of whether `calculate_now` is
- // true, i.e. if `calculate_now` is true, this request is effectively merged
- // with the in progress calculation.
- if (topics_calculator_) {
- get_state_for_webui_callbacks_.push_back(std::move(callback));
- return;
- }
- DCHECK(schedule_calculate_timer_.IsRunning());
- if (calculate_now) {
- get_state_for_webui_callbacks_.push_back(std::move(callback));
- schedule_calculate_timer_.AbandonAndStop();
- CalculateBrowsingTopics();
- return;
- }
- std::move(callback).Run(GetBrowsingTopicsStateForWebUiHelper());
- }
- std::vector<privacy_sandbox::CanonicalTopic>
- BrowsingTopicsServiceImpl::GetTopicsForSiteForDisplay(
- const url::Origin& top_origin) const {
- if (!browsing_topics_state_loaded_)
- return {};
- std::string top_domain =
- net::registry_controlled_domains::GetDomainAndRegistry(
- top_origin.GetURL(),
- net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
- std::vector<privacy_sandbox::CanonicalTopic> result;
- for (const EpochTopics* epoch :
- browsing_topics_state_.EpochsForSite(top_domain)) {
- absl::optional<Topic> topic = epoch->TopicForSiteForDisplay(
- top_domain, browsing_topics_state_.hmac_key());
- if (!topic)
- continue;
- // `epoch->TopicForSiteForDisplay()` shall only return a top topic, and a
- // top topic can never be in the disallowed state (i.e. it will be cleared
- // when it becomes diallowed).
- DCHECK(privacy_sandbox_settings_->IsTopicAllowed(
- privacy_sandbox::CanonicalTopic(*topic, epoch->taxonomy_version())));
- result.emplace_back(*topic, epoch->taxonomy_version());
- }
- return result;
- }
- std::vector<privacy_sandbox::CanonicalTopic>
- BrowsingTopicsServiceImpl::GetTopTopicsForDisplay() const {
- if (!browsing_topics_state_loaded_)
- return {};
- std::vector<privacy_sandbox::CanonicalTopic> result;
- for (const EpochTopics& epoch : browsing_topics_state_.epochs()) {
- DCHECK_LE(epoch.padded_top_topics_start_index(),
- epoch.top_topics_and_observing_domains().size());
- for (size_t i = 0; i < epoch.padded_top_topics_start_index(); ++i) {
- const TopicAndDomains& topic_and_domains =
- epoch.top_topics_and_observing_domains()[i];
- if (!topic_and_domains.IsValid())
- continue;
- // A top topic can never be in the disallowed state (i.e. it will be
- // cleared when it becomes diallowed).
- DCHECK(privacy_sandbox_settings_->IsTopicAllowed(
- privacy_sandbox::CanonicalTopic(topic_and_domains.topic(),
- epoch.taxonomy_version())));
- result.emplace_back(topic_and_domains.topic(), epoch.taxonomy_version());
- }
- }
- return result;
- }
- void BrowsingTopicsServiceImpl::ClearTopic(
- const privacy_sandbox::CanonicalTopic& canonical_topic) {
- if (!browsing_topics_state_loaded_)
- return;
- browsing_topics_state_.ClearTopic(canonical_topic.topic_id(),
- canonical_topic.taxonomy_version());
- }
- void BrowsingTopicsServiceImpl::ClearTopicsDataForOrigin(
- const url::Origin& origin) {
- if (!browsing_topics_state_loaded_)
- return;
- std::string context_domain =
- net::registry_controlled_domains::GetDomainAndRegistry(
- origin.GetURL(),
- net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
- HashedDomain hashed_context_domain = HashContextDomainForStorage(
- browsing_topics_state_.hmac_key(), context_domain);
- browsing_topics_state_.ClearContextDomain(hashed_context_domain);
- site_data_manager_->ClearContextDomain(hashed_context_domain);
- }
- void BrowsingTopicsServiceImpl::ClearAllTopicsData() {
- if (!browsing_topics_state_loaded_)
- return;
- browsing_topics_state_.ClearAllTopics();
- site_data_manager_->ExpireDataBefore(base::Time::Now());
- }
- std::unique_ptr<BrowsingTopicsCalculator>
- BrowsingTopicsServiceImpl::CreateCalculator(
- privacy_sandbox::PrivacySandboxSettings* privacy_sandbox_settings,
- history::HistoryService* history_service,
- content::BrowsingTopicsSiteDataManager* site_data_manager,
- optimization_guide::PageContentAnnotationsService* annotations_service,
- const base::circular_deque<EpochTopics>& epochs,
- BrowsingTopicsCalculator::CalculateCompletedCallback callback) {
- return std::make_unique<BrowsingTopicsCalculator>(
- privacy_sandbox_settings, history_service, site_data_manager,
- annotations_service, epochs, std::move(callback));
- }
- const BrowsingTopicsState& BrowsingTopicsServiceImpl::browsing_topics_state() {
- return browsing_topics_state_;
- }
- void BrowsingTopicsServiceImpl::ScheduleBrowsingTopicsCalculation(
- base::TimeDelta delay) {
- DCHECK(browsing_topics_state_loaded_);
- // `this` owns the timer, which is automatically cancelled on destruction, so
- // base::Unretained(this) is safe.
- schedule_calculate_timer_.Start(
- FROM_HERE, delay,
- base::BindOnce(&BrowsingTopicsServiceImpl::CalculateBrowsingTopics,
- base::Unretained(this)));
- }
- void BrowsingTopicsServiceImpl::CalculateBrowsingTopics() {
- DCHECK(browsing_topics_state_loaded_);
- DCHECK(!topics_calculator_);
- // `this` owns `topics_calculator_` so `topics_calculator_` should not invoke
- // the callback once it's destroyed.
- topics_calculator_ = CreateCalculator(
- privacy_sandbox_settings_, history_service_, site_data_manager_,
- annotations_service_, browsing_topics_state_.epochs(),
- base::BindOnce(
- &BrowsingTopicsServiceImpl::OnCalculateBrowsingTopicsCompleted,
- base::Unretained(this)));
- }
- void BrowsingTopicsServiceImpl::OnCalculateBrowsingTopicsCompleted(
- EpochTopics epoch_topics) {
- DCHECK(browsing_topics_state_loaded_);
- DCHECK(topics_calculator_);
- topics_calculator_.reset();
- browsing_topics_state_.AddEpoch(std::move(epoch_topics));
- browsing_topics_state_.UpdateNextScheduledCalculationTime();
- ScheduleBrowsingTopicsCalculation(
- blink::features::kBrowsingTopicsTimePeriodPerEpoch.Get());
- if (!get_state_for_webui_callbacks_.empty()) {
- mojom::WebUIGetBrowsingTopicsStateResultPtr webui_state =
- GetBrowsingTopicsStateForWebUiHelper();
- for (auto& callback : get_state_for_webui_callbacks_) {
- std::move(callback).Run(webui_state->Clone());
- }
- get_state_for_webui_callbacks_.clear();
- }
- }
- void BrowsingTopicsServiceImpl::OnBrowsingTopicsStateLoaded() {
- DCHECK(!browsing_topics_state_loaded_);
- browsing_topics_state_loaded_ = true;
- base::Time browsing_topics_data_sccessible_since =
- privacy_sandbox_settings_->TopicsDataAccessibleSince();
- StartupCalculateDecision decision = GetStartupCalculationDecision(
- browsing_topics_state_, browsing_topics_data_sccessible_since,
- base::BindRepeating(
- &privacy_sandbox::PrivacySandboxSettings::IsTopicAllowed,
- base::Unretained(privacy_sandbox_settings_)));
- if (decision.clear_topics_data)
- browsing_topics_state_.ClearAllTopics();
- site_data_manager_->ExpireDataBefore(browsing_topics_data_sccessible_since);
- ScheduleBrowsingTopicsCalculation(decision.next_calculation_delay);
- }
- void BrowsingTopicsServiceImpl::Shutdown() {
- privacy_sandbox_settings_observation_.Reset();
- history_service_observation_.Reset();
- }
- void BrowsingTopicsServiceImpl::OnTopicsDataAccessibleSinceUpdated() {
- if (!browsing_topics_state_loaded_)
- return;
- // Here we rely on the fact that `browsing_topics_data_accessible_since` can
- // only be updated to base::Time::Now() due to data deletion. In this case, we
- // should just clear all topics.
- browsing_topics_state_.ClearAllTopics();
- site_data_manager_->ExpireDataBefore(
- privacy_sandbox_settings_->TopicsDataAccessibleSince());
- // Abort the outstanding topics calculation and restart immediately.
- if (topics_calculator_) {
- DCHECK(!schedule_calculate_timer_.IsRunning());
- topics_calculator_.reset();
- CalculateBrowsingTopics();
- }
- }
- void BrowsingTopicsServiceImpl::OnURLsDeleted(
- history::HistoryService* history_service,
- const history::DeletionInfo& deletion_info) {
- if (!browsing_topics_state_loaded_)
- return;
- // Ignore invalid time_range.
- if (!deletion_info.IsAllHistory() && !deletion_info.time_range().IsValid())
- return;
- for (size_t i = 0; i < browsing_topics_state_.epochs().size(); ++i) {
- const EpochTopics& epoch_topics = browsing_topics_state_.epochs()[i];
- if (epoch_topics.empty())
- continue;
- // The typical case is assumed here. We cannot always derive the original
- // history start time, as the necessary data (e.g. its previous epoch's
- // calculation time) may have been gone.
- base::Time history_data_start_time =
- epoch_topics.calculation_time() -
- blink::features::kBrowsingTopicsTimePeriodPerEpoch.Get();
- bool time_range_overlap =
- epoch_topics.calculation_time() >= deletion_info.time_range().begin() &&
- history_data_start_time <= deletion_info.time_range().end();
- if (time_range_overlap)
- browsing_topics_state_.ClearOneEpoch(i);
- }
- // If there's an outstanding topics calculation, abort and restart it.
- if (topics_calculator_) {
- DCHECK(!schedule_calculate_timer_.IsRunning());
- topics_calculator_.reset();
- CalculateBrowsingTopics();
- }
- }
- mojom::WebUIGetBrowsingTopicsStateResultPtr
- BrowsingTopicsServiceImpl::GetBrowsingTopicsStateForWebUiHelper() {
- DCHECK(browsing_topics_state_loaded_);
- DCHECK(!topics_calculator_);
- auto webui_state = mojom::WebUIBrowsingTopicsState::New();
- webui_state->next_scheduled_calculation_time =
- browsing_topics_state_.next_scheduled_calculation_time();
- for (const EpochTopics& epoch : browsing_topics_state_.epochs()) {
- DCHECK_LE(epoch.padded_top_topics_start_index(),
- epoch.top_topics_and_observing_domains().size());
- // Note: for a failed epoch calculation, the default zero-initialized values
- // will be displayed in the Web UI.
- auto webui_epoch = mojom::WebUIEpoch::New();
- webui_epoch->calculation_time = epoch.calculation_time();
- webui_epoch->model_version = base::NumberToString(epoch.model_version());
- webui_epoch->taxonomy_version =
- base::NumberToString(epoch.taxonomy_version());
- for (size_t i = 0; i < epoch.top_topics_and_observing_domains().size();
- ++i) {
- const TopicAndDomains& topic_and_domains =
- epoch.top_topics_and_observing_domains()[i];
- privacy_sandbox::CanonicalTopic canonical_topic =
- privacy_sandbox::CanonicalTopic(topic_and_domains.topic(),
- epoch.taxonomy_version());
- std::vector<std::string> webui_observed_by_domains;
- webui_observed_by_domains.reserve(
- topic_and_domains.hashed_domains().size());
- for (const auto& domain : topic_and_domains.hashed_domains()) {
- webui_observed_by_domains.push_back(
- base::NumberToString(domain.value()));
- }
- // Note: if the topic is invalid (i.e. cleared), the output `topic_id`
- // will be 0; if the topic is invalid, or if the taxonomy version isn't
- // recognized by this Chrome binary, the output `topic_name` will be
- // "Unknown".
- auto webui_topic = mojom::WebUITopic::New();
- webui_topic->topic_id = topic_and_domains.topic().value();
- webui_topic->topic_name = canonical_topic.GetLocalizedRepresentation();
- webui_topic->is_real_topic = (i < epoch.padded_top_topics_start_index());
- webui_topic->observed_by_domains = std::move(webui_observed_by_domains);
- webui_epoch->topics.push_back(std::move(webui_topic));
- }
- webui_state->epochs.push_back(std::move(webui_epoch));
- }
- // Reorder the epochs from latest to oldest.
- base::ranges::reverse(webui_state->epochs);
- return mojom::WebUIGetBrowsingTopicsStateResult::NewBrowsingTopicsState(
- std::move(webui_state));
- }
- } // namespace browsing_topics
|