1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #ifndef COMPONENTS_BROWSING_TOPICS_BROWSING_TOPICS_STATE_H_
6 #define COMPONENTS_BROWSING_TOPICS_BROWSING_TOPICS_STATE_H_
8 #include "base/containers/queue.h"
9 #include "base/files/important_file_writer.h"
10 #include "base/gtest_prod_util.h"
11 #include "base/task/sequenced_task_runner.h"
12 #include "base/time/time.h"
13 #include "components/browsing_topics/common/common_types.h"
14 #include "components/browsing_topics/epoch_topics.h"
16 namespace browsing_topics {
18 // Contains the data needed to calculate the browsing topics when a context
19 // requests it via document.browsingTopics(). The data is backed by a JSON file:
20 // when `BrowsingTopicsState` is initialized, the state members will be read
21 // from the file on a backend thread, and all overwriting methods will schedule
22 // an update to the file. The `BrowsingTopicsState`'s owner should listen on the
23 // `loaded_callback` notification. Before the loading finishes, it's disallowed
24 // to access this `BrowsingTopicsState`.
25 class BrowsingTopicsState
26 : public base::ImportantFileWriter::BackgroundDataSerializer {
29 LoadResult(bool file_exists, std::unique_ptr<base::Value> value);
32 LoadResult(const LoadResult&) = delete;
33 LoadResult& operator=(const LoadResult&) = delete;
34 LoadResult(LoadResult&&) = delete;
35 LoadResult& operator=(LoadResult&&) = delete;
37 bool file_exists = false;
39 // The deserialized value from the content of the json file.
40 std::unique_ptr<base::Value> value;
43 // The result of parsing a `LoadResult::value` to the `BrowsingTopicsState`.
45 // Whether the parsing was successful. Parsing can fail due to corrupted
49 // Whether `BrowsingTopicsState` should be saved to the file after parsing.
50 // Saving is needed if the config version has been updated, or if an error
51 // is encountered (to clean up unneeded data). In the common case where the
52 // data is loaded from a pre-existing file, the file save isn't necessary.
53 bool should_save_state_to_file = false;
56 explicit BrowsingTopicsState(const base::FilePath& profile_path,
57 base::OnceClosure loaded_callback);
59 ~BrowsingTopicsState() override;
61 BrowsingTopicsState(const BrowsingTopicsState&) = delete;
62 BrowsingTopicsState& operator=(const BrowsingTopicsState&) = delete;
63 BrowsingTopicsState(BrowsingTopicsState&&) = delete;
64 BrowsingTopicsState& operator=(BrowsingTopicsState&&) = delete;
67 void ClearAllTopics();
69 // Clear the topics data at `epochs_[epoch_index]`. Note that this doesn't
70 // remove the entry from `epochs_`.
71 void ClearOneEpoch(size_t epoch_index);
73 // Clear the topic and observing domains data for `topic`.
74 void ClearTopic(Topic topic);
76 // Clear the observing domains data in `epochs_` that match
77 // `hashed_context_domain`.
78 void ClearContextDomain(const HashedDomain& hashed_context_domain);
80 // Append `epoch_topics` to `epochs_`. This is invoked at the end of each
81 // epoch calculation. If an old EpochTopics is removed as a result, return it.
82 absl::optional<EpochTopics> AddEpoch(EpochTopics epoch_topics);
84 // Set `next_scheduled_calculation_time_` to one epoch later from
85 // base::Time::Now(). This is invoked at the end of each epoch calculation.
86 void UpdateNextScheduledCalculationTime();
88 // Calculate the candidate epochs to derive the topics from on `top_domain`.
89 // The caller (i.e. BrowsingTopicsServiceImpl, which also holds `this`) is
90 // responsible for ensuring that the `EpochTopic` objects that the pointers
91 // refer to remain alive when the caller is accessing them.
92 std::vector<const EpochTopics*> EpochsForSite(
93 const std::string& top_domain) const;
95 const base::circular_deque<EpochTopics>& epochs() const {
100 base::Time next_scheduled_calculation_time() const {
102 return next_scheduled_calculation_time_;
105 ReadOnlyHmacKey hmac_key() const {
110 bool HasScheduledSaveForTesting() const;
113 FRIEND_TEST_ALL_PREFIXES(BrowsingTopicsStateTest,
114 EpochsForSite_OneEpoch_SwitchTimeNotArrived);
115 FRIEND_TEST_ALL_PREFIXES(BrowsingTopicsStateTest,
116 EpochsForSite_OneEpoch_SwitchTimeArrived);
117 FRIEND_TEST_ALL_PREFIXES(BrowsingTopicsStateTest,
118 EpochsForSite_OneEpoch_ManuallyTriggered);
120 base::TimeDelta CalculateSiteStickyTimeDelta(
121 const std::string& top_domain) const;
123 // ImportantFileWriter::BackgroundDataSerializer implementation.
124 base::ImportantFileWriter::BackgroundDataProducerCallback
125 GetSerializedDataProducerForBackgroundSequence() override;
127 base::Value::Dict ToDictValue() const;
131 void DidLoadFile(base::OnceClosure loaded_callback,
132 std::unique_ptr<LoadResult> load_result);
134 // Parse `value` and populate the state member variables.
135 ParseResult ParseValue(const base::Value& value);
137 // Sequenced task runner where disk writes will be performed.
138 scoped_refptr<base::SequencedTaskRunner> backend_task_runner_;
140 // Helper to write data safely.
141 base::ImportantFileWriter writer_;
143 // Contains the browsing topics of the latest epochs, as well as the topics
144 // observed by each context domain in each of the epoch. These entries are in
145 // time ascending order: a new entry will be appended to `epochs_` on every
146 // browsing topics calculation, regardless of whether it succeeded or not. We
147 // are only interested in the latest
148 // `kBrowsingTopicsNumberOfEpochsToExpose + 1` epochs (i.e. the epoch
149 // switching time will be per-user, per-site, with a full epoch range of
150 // variance, thus one extra epoch are kept here), so old data will be
151 // automatically removed, and the size of the queue won't exceed that limit.
152 base::circular_deque<EpochTopics> epochs_;
154 // The next time a calculation should occur. This will be updated when a
155 // calculation is scheduled at the end of a topics calculation and is always
156 // synchronously updated with `epochs_`.
158 // next_scheduled_calculation_time_.is_null() indicates this is a new profile
159 // or there was an update to the configuration version when this
160 // `BrowsingTopicsState` is initialized. In either case, `epochs_` will be
162 base::Time next_scheduled_calculation_time_;
164 // The key for calculating the per-user hash numbers. See ./util.h for various
165 // use cases. This key is generated and synced to storage in the first
166 // browsing session. It won't be reset/updated in any case.
169 // Whether the state members are loaded from file. Public accessor methods are
170 // disallowed (except for `HasScheduledSaveForTesting`) before `loaded_`
172 bool loaded_ = false;
174 base::WeakPtrFactory<BrowsingTopicsState> weak_ptr_factory_{this};
177 } // namespace browsing_topics
179 #endif // COMPONENTS_BROWSING_TOPICS_BROWSING_TOPICS_STATE_H_