1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_VISITEDLINK_BROWSER_PARTITIONED_VISITEDLINK_WRITER_H_
#define COMPONENTS_VISITEDLINK_BROWSER_PARTITIONED_VISITEDLINK_WRITER_H_
#include <map>
#include <set>
#include "base/memory/read_only_shared_memory_region.h"
#include "components/visitedlink/common/visitedlink_common.h"
#include "components/visitedlink/core/visited_link.h"
#include "content/public/browser/browser_context.h"
namespace base {
class WritableSharedMemoryMapping;
}
namespace url {
class Origin;
}
namespace visitedlink {
class VisitedLinkDelegate;
// PartitionedVisitedLinkWriter constructs and writes to the partitioned
// :visited links hashtable. There should only be one instance of
// PartitionedVisitedLinkWriter, and it must be initialized before use.
//
// Much of this code is similar to or identical to the (unpartitioned)
// VisitedLinkWriter class. PartitionedVisitedLinkWriter does not persist to
// disk, the code has been "forked" into a separate class that relies on the
// HistoryService's VisitedLinkDatabase to persist partitioned :visited link
// browsing history across sessions. Once constructed from the
// VisitedLinkDatabase, the partitioned hashtable is stored in a shared memory
// instance.
class PartitionedVisitedLinkWriter : public VisitedLinkCommon {
public:
// Listens to the link coloring database events. The writer is given this
// event as a constructor argument and dispatches events using it.
class Listener {
public:
virtual ~Listener() = default;
// Called when link coloring database has been created or replaced. The
// argument is a memory region containing the new table.
virtual void NewTable(base::ReadOnlySharedMemoryRegion* table_region) = 0;
// Called when new link has been added. The argument is the fingerprint
// (hash) of the link.
virtual void Add(Fingerprint fingerprint) = 0;
// Called when link coloring state has been reset. This may occur when
// entire or parts of history were deleted. Also this may occur when
// the table was rebuilt or loaded. The salt is stored in the database file.
// As a result the salt will change after loading the table from the
// database file. In this case we use |invalidate_hashes| to inform that
// all cached visitedlink hashes need to be recalculated.
virtual void Reset(bool invalidate_hashes) = 0;
// This function determines the per-origin salts required for
// any navigations that took place during the hashtable build (and as a
// result did not send a per-origin salt in the navigation params - see
// PartitionedVisitedLinkWriter::salts_ for more information). The
// per-origin salts are sent via IPC to their respective VisitedLinkReader
// instances.
// NOTE: this is called on the main thread once the hashtable has
// completed building on the DB thread.
virtual void UpdateOriginSalts() = 0;
};
PartitionedVisitedLinkWriter(content::BrowserContext* browser_context,
VisitedLinkDelegate* delegate);
// This constructor is used by unit tests.
PartitionedVisitedLinkWriter(std::unique_ptr<Listener> listener,
VisitedLinkDelegate* delegate,
bool suppress_build,
int32_t default_table_size);
~PartitionedVisitedLinkWriter() override;
// Must be called immediately after object creation. Nothing else will work
// until this is called. Returns true on success, false means that this
// object won't work.
bool Init();
// Adds a VisitedLink to the hashtable.
void AddVisitedLink(const VisitedLink& link);
// See DeleteURLs.
class VisitedLinkIterator {
public:
// HasNextVisitedLink must return true when this is called. Returns the next
// VisitedLink then advances the iterator. Note that the returned reference
// is only valid until the next call of NextVisitedLink.
virtual const VisitedLink& NextVisitedLink() = 0;
// Returns true if still has VisitedLinks to be iterated.
virtual bool HasNextVisitedLink() const = 0;
protected:
virtual ~VisitedLinkIterator() = default;
};
// Deletes the specified VisitedLinks from the hashtable.
void DeleteVisitedLinks(VisitedLinkIterator* iterator);
// Clears all VisitedLinks from the hashtable.
void DeleteAllVisitedLinks();
// Return the salt used to hash visited links from this origin. If we have not
// visited this origin before, a new <origin, salt> pair will be added to the
// map, and that new salt value will be retuned. Will return
// std::optional if the table is currently being built or rebuilt.
//
// NOTE: THIS FUNCTION MAY ONLY BE CALLED ON THE MAIN (UI) THREAD.
std::optional<uint64_t> GetOrAddOriginSalt(const url::Origin& origin);
#if defined(UNIT_TEST) || !defined(NDEBUG) || defined(PERF_TEST)
// This is a debugging function that can be called to double-check internal
// data structures. It will assert if the check fails.
void DebugValidate();
// Sets a task to execute when we've completed building the table from
// history. This is ONLY used by unit tests to wait for the build to complete
// before they continue. The pointer will be owned by this object after the
// call.
void set_build_complete_task(base::OnceClosure task) {
DCHECK(build_complete_task_.is_null());
build_complete_task_ = std::move(task);
}
// Returns the number non-null hashes in the table for testing verification.
int32_t GetUsedCount() const { return used_items_; }
// Returns the listener.
PartitionedVisitedLinkWriter::Listener* GetListener() const {
return listener_.get();
}
// Returns the hashtable stored in memory.
const base::MappedReadOnlyRegion& GetMappedTableMemoryForTesting() {
return mapped_table_memory_;
}
#endif
private:
FRIEND_TEST_ALL_PREFIXES(PartitionedVisitedLinkTest, DeleteWithCollisions);
FRIEND_TEST_ALL_PREFIXES(PartitionedVisitedLinkTest, HashRangeWraparound);
// When creating an empty table, we use this many entries (see the .cc file).
static const unsigned kDefaultTableSize;
// Object to build the table on the history thread (see the .cc file).
class TableBuilder;
// General Table Handling ----------------------------------------------------
// Creates an empty partitioned hashtable. The table is populated with the
// partitioned shared header and filled with null hashes. The result of
// allocation is saved into `mapped_table_memory`.
bool CreateVisitedLinkTable(int32_t num_entries);
// Allocates the Fingerprint structure and length. Returns true on success.
static bool CreateVisitedLinkTableHelper(int32_t num_entries,
base::MappedReadOnlyRegion* memory);
// ResizeTableIfNecessary will check to see if the table should be resized and
// calls ResizeTable if needed. Returns true if we decided to resize the
// table.
bool ResizeTableIfNecessary();
// Resizes the table (growing or shrinking) as necessary to accommodate the
// current count.
void ResizeTable(int32_t new_size);
// Computes the table load as fraction. For example, if 1/4 of the entries are
// full, this value will be 0.25
float ComputeTableLoad() const {
return static_cast<float>(used_items_) / static_cast<float>(table_length_);
}
// Populates the partitioned hashtable based on the browser history stored
// in the VisitedLinkDatabase. This will set table_builder_ while working, and
// there should not already be a build occurring when called. See the
// definition for more details on how this works.
//
// Returns true on success. Failure means we're not attempting to build
// from the VisitedLinkDatabase because something failed.
bool BuildTableFromDelegate();
// Callback that the table builder uses when the build is complete.
// `success` is true if the fingerprint generation succeeded, in which case
// `fingerprints` will contain the computed fingerprints. On failure, there
// will be no fingerprints. `salts` will contain the origin salts used to
// generate the fingerprints. On failure, there will be no salts.
void OnTableBuildComplete(bool success,
const std::vector<Fingerprint>& fingerprints,
std::map<url::Origin, uint64_t> salts);
// If a build is in progress, we save `link` in `added_during_build`.
// Otherwise, we add to the hashtable. Returns the index of the inserted
// fingerprint or `null_hash_` on failure.
Hash TryToAddVisitedLink(const VisitedLink& link);
// Increases or decreases the given hash value by one, wrapping around as
// necessary. Used for probing.
inline Hash IncrementHash(Hash hash) {
if (hash >= table_length_ - 1) {
return 0; // Wrap around.
}
return hash + 1;
}
inline Hash DecrementHash(Hash hash) {
if (hash <= 0) {
return table_length_ - 1; // Wrap around.
}
return hash - 1;
}
// Called to add a fingerprint to the table. Returns the index of the inserted
// fingerprint or null_hash_ if there was a duplicate and this item was
// skipped.
//
// TODO(crbug.com/332364003): If `send_notifications` is true
// and the item is added successfully, Listener::Add will be invoked.
Hash AddFingerprint(Fingerprint fingerprint, bool send_notifications);
// Deletes all fingerprints from the given vector from the current hashtable.
// This does not update the `deleted_during_build_` list, the caller must
// update this itself if there is a build pending.
void DeleteFingerprintsFromCurrentTable(
const std::set<Fingerprint>& fingerprints);
// Removes the indicated fingerprint from the table. Returns true if the
// fingerprint was deleted, false if it was not in the table to delete.
bool DeleteFingerprint(Fingerprint fingerprint);
// Returns a pointer to the start of the hash table, given the mapping
// containing the hash table.
static Fingerprint* GetHashTableFromMapping(
base::WritableSharedMemoryMapping& hash_table_mapping);
// Returns the default table size. It can be overridden in unit tests.
uint32_t DefaultTableSize() const;
// Returns the desired table size for storing `item_count` visited links.
uint32_t NewTableSizeForCount(int32_t item_count) const;
// Member variables ----------------------------------------------------------
// TODO(crbug.com/332364003): We need to create an instance of
// VisitedLinkEventListener to handle incoming events and define the Listener
// class in the .h file.
// When non-NULL, indicates we are building the hashtable from the
// VisitedLinkDatabase.
scoped_refptr<TableBuilder> table_builder_;
// Contains the fingerprints of visited links that have been added or deleted
// on the main thread since we started building the hashtable on the DB
// thread.
std::set<VisitedLink> added_during_build_;
std::set<VisitedLink> deleted_during_build_;
// Shared memory consisting of a PartitionedSharedHeader followed by the
// table.
base::MappedReadOnlyRegion mapped_table_memory_;
// Number of non-empty items in the table, used to compute fullness.
int32_t used_items_ = 0;
// Reference to the browser context that this object belongs to
// (it knows the path to where the data is stored).
raw_ptr<content::BrowserContext> browser_context_ = nullptr;
// Client owns the delegate and is responsible for it being valid through
// the lifetime this PartitionedVisitedLinkWriter.
raw_ptr<VisitedLinkDelegate> delegate_;
// VisitedLinkEventListener to handle incoming events.
std::unique_ptr<Listener> listener_;
// Contains every per-origin salt used in creating the hashtable. Callers
// should only access on the main (UI) thread.
//
// NOTE: When VisitedLinkWriter is created, `salts_` is initially empty.
// The <origin, salt> pairs populating the map are calculated on a background
// thread and assigned on the main thread. When this is happening,
// `table_builder_` is non-null, and `salts_` CANNOT be added to or accessed
// by the UI thread.
//
// Once initialization is complete and `table_builder_` is set to null again,
// `salts_` can be added to and accessed by the UI thread, whether we are
// adding new visits via the History Service or sending salt values via the
// VisitedLinksNavigationThrottle.
//
// TODO(crbug.com/330548738): Currently we store all salts relevant to this
// profile in this one map, but there can be many StoragePartitions per
// profile. We should revisit in a future phase to take into account which
// StoragePartition each origin is being committed to.
std::map<url::Origin, uint64_t> salts_;
// Testing values ----------------------------------------------------------
// Set to fail CreateVisitedLinkTable(), to simulate shared memory allocation
// failure. This is used for testing, will be false in production.
static bool fail_table_creation_for_testing_;
// Set to prevent us from building from the VisitedLinkDatabase. This is used
// for testing and will be false in production.
const bool suppress_build_ = false;
// When nonzero, overrides the table size for testing.
const int32_t table_size_override_ = 0;
// When set, indicates the task that should be run after the next build from
// history is complete.
base::OnceClosure build_complete_task_;
base::WeakPtrFactory<PartitionedVisitedLinkWriter> weak_ptr_factory_{this};
};
// NOTE: These methods are defined inline here, so we can share the compilation
// of partitioned_visitedlink_writer.cc between the browser and the unit/perf
// tests.
#if defined(UNIT_TEST) || defined(PERF_TEST) || !defined(NDEBUG)
inline void PartitionedVisitedLinkWriter::DebugValidate() {
int32_t used_count = 0;
for (int32_t i = 0; i < table_length_; i++) {
if (hash_table_[i]) {
used_count++;
}
}
DCHECK_EQ(used_count, used_items_);
}
#endif
} // namespace visitedlink
#endif // COMPONENTS_VISITEDLINK_BROWSER_PARTITIONED_VISITEDLINK_WRITER_H_
|