blob: b19905b82ae93ec802d148c6b39a412e6c8f3220 [file] [log] [blame] [edit]
// 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.
#include "components/visitedlink/browser/partitioned_visitedlink_writer.h"
#include <array>
#include "base/compiler_specific.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/shared_memory_mapping.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/trace_event/trace_event.h"
#include "components/visitedlink/browser/visitedlink_delegate.h"
#include "components/visitedlink/browser/visitedlink_event_listener.h"
#include "components/visitedlink/core/visited_link.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/schemeful_site.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. NOTE: Please also keep in line with
// components/visitedlink/browser/visitedlink_writer.cc:AddFingerprint.
//
// LINT.IfChange(AddFingerprint)
enum class AddFingerprint {
kNewVisit = 0,
kAlreadyVisited = 1,
kTableError = 2,
kMaxValue = kTableError,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/history/enums.xml:AddFingerprint)
} // namespace
namespace visitedlink {
// This value should also be the same as the smallest size in the lookup
// table in NewTableSizeForCount (prime number).
const unsigned PartitionedVisitedLinkWriter::kDefaultTableSize = 16381;
bool PartitionedVisitedLinkWriter::fail_table_creation_for_testing_ = false;
// TableBuilder ---------------------------------------------------------------
// How building from history works
// ---------------------------------
//
// We mark that we're building our hashtable from history by setting the
// table_builder_ member in PartitionedVisitedLinkWriter to the TableBuilder we
// create. This builder will be called on the history (DB) thread by the history
// system for every partition key in the VisitedLinkDatabase.
//
// The builder will store the fingerprints for those links, as well as the
// origin salts used to calculate those fingerprints, and then marshalls
// back to the main (UI) thread where the PartitionedVisitedLinkWriter will be
// notified. The writer then replaces its empty table with a new table
// containing the computed fingerprints. The map of origin salts is copied into
// `salts_` and the UI thread is allowed to get or add to the map itself.
//
// The builder must remain active while the history system is using it. If the
// WeakPtr to the PartitionedVisitedLinkWriter is severed during table build, no
// callback will be executed once we are marshalled back to the UI thread.
class PartitionedVisitedLinkWriter::TableBuilder
: public VisitedLinkDelegate::VisitedLinkEnumerator {
public:
explicit TableBuilder(base::WeakPtr<PartitionedVisitedLinkWriter> writer);
TableBuilder(const TableBuilder&) = delete;
TableBuilder& operator=(const TableBuilder&) = delete;
// VisitedLinkDelegate::VisitedLinkEnumerator overrides.
void OnVisitedLink(const GURL& link_url,
const net::SchemefulSite& top_level_site,
const url::Origin& frame_origin) override;
void OnVisitedLinkComplete(bool success) override;
private:
~TableBuilder() override = default;
// When building the partitioned hashtable, we need to get or add <origin,
// salt> pairs to our salts map, as these salts will be used in generating the
// fingerprints stored in the hashtable. However, to avoid threading
// discrepancies, the UI thread should not be able to access or alter the salt
// map while the table is building on the DB thread.
//
// As a result, we keep a local copy of our salt map (`local_salts_`) in
// TableBuilder, and call this function to get from it or add to it. Once we
// return to the UI thread, we will copy `local_salts_` to
// PartitionedVisitedLinkWriter's `salts_` and allow the UI thread access.
uint64_t GetOrAddLocalOriginSalt(const url::Origin& origin);
// OnComplete mashals to this function on the main (UI) thread to do the
// notification.
void OnCompleteMainThread();
// Owner of this object. MAY ONLY BE ACCESSED ON THE MAIN (UI) THREAD!
base::WeakPtr<PartitionedVisitedLinkWriter> writer_;
// Indicates whether or not table building has failed.
bool success_ = true;
// Stores the fingerprints we computed on the background thread.
VisitedLinkCommon::Fingerprints fingerprints_;
// Stores the salts we computed on the background thread. See
// GetOrAddLocalOriginSalt() above for more details.
std::map<url::Origin, uint64_t> local_salts_;
};
// TableBuilder ----------------------------------------------------
PartitionedVisitedLinkWriter::TableBuilder::TableBuilder(
base::WeakPtr<PartitionedVisitedLinkWriter> writer)
: writer_(std::move(writer)) {}
void PartitionedVisitedLinkWriter::TableBuilder::OnVisitedLink(
const GURL& link_url,
const net::SchemefulSite& top_level_site,
const url::Origin& frame_origin) {
// We only want to store valid visited links in the partitioned hashtable.
// Otherwise we cannot determine if they are visited in the renderer.
VisitedLink link = {link_url, top_level_site, frame_origin};
if (!link.IsValid()) {
return;
}
// Attempt to add this visited link to the partitioned hashtable.
const uint64_t salt = GetOrAddLocalOriginSalt(frame_origin);
fingerprints_.push_back(
VisitedLinkWriter::ComputePartitionedFingerprint(link, salt));
// Attempt to add the self-link version of this visited links to the
// partitioned hashtable if the feature is enabled.
if (base::FeatureList::IsEnabled(
blink::features::kPartitionVisitedLinkDatabaseWithSelfLinks)) {
std::optional<VisitedLink> self_link = link.MaybeCreateSelfLink();
if (self_link.has_value()) {
const uint64_t self_salt =
GetOrAddLocalOriginSalt(self_link->frame_origin);
fingerprints_.push_back(VisitedLinkWriter::ComputePartitionedFingerprint(
self_link.value(), self_salt));
}
}
}
// NOTE: in prod, this function should not be called on the UI thread.
void PartitionedVisitedLinkWriter::TableBuilder::OnVisitedLinkComplete(
bool success) {
success_ = success;
DLOG_IF(WARNING, !success)
<< "Unable to build visited links hashtable from VisitedLinkDatabase";
// Marshall to the main thread where we can access writer_ and notify it
// of the results.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&PartitionedVisitedLinkWriter::TableBuilder::OnCompleteMainThread,
this));
}
uint64_t PartitionedVisitedLinkWriter::TableBuilder::GetOrAddLocalOriginSalt(
const url::Origin& origin) {
// Obtain the salt for this origin if it already exists.
auto it = local_salts_.find(origin);
if (it != local_salts_.end()) {
return it->second;
}
// Otherwise, generate a new salt for this origin.
const uint64_t generated_salt = base::RandUint64();
local_salts_.insert({origin, generated_salt});
return generated_salt;
}
void PartitionedVisitedLinkWriter::TableBuilder::OnCompleteMainThread() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (writer_) {
// Marshal to the main thread with the resulting fingerprints and salts.
writer_->OnTableBuildComplete(success_, fingerprints_,
std::move(local_salts_));
}
}
// PartitionedVisitedLinkWriter
// ----------------------------------------------------------
PartitionedVisitedLinkWriter::PartitionedVisitedLinkWriter(
content::BrowserContext* browser_context,
VisitedLinkDelegate* delegate)
: browser_context_(browser_context),
delegate_(delegate),
listener_(
std::make_unique<VisitedLinkEventListener>(browser_context, this)) {}
PartitionedVisitedLinkWriter::PartitionedVisitedLinkWriter(
std::unique_ptr<Listener> listener,
VisitedLinkDelegate* delegate,
bool suppress_build,
int32_t default_table_size)
: delegate_(delegate),
listener_(std::move(listener)),
suppress_build_(suppress_build),
table_size_override_(default_table_size) {
DCHECK(listener_);
}
PartitionedVisitedLinkWriter::~PartitionedVisitedLinkWriter() = default;
bool PartitionedVisitedLinkWriter::Init() {
TRACE_EVENT0("browser", "PartitionedVisitedLinkWriter::Init");
// Create a temporary table in mapped_table_memory_ full of null hashes. While
// we build the table from history on the DB thread, this temporary
// table will be available to query on the UI thread.
if (!CreateVisitedLinkTable(DefaultTableSize())) {
return false;
}
// When enabled in unit tests, prevents building from the VisitedLinkDatabase.
// Resulting hashtable is of size `DefaultTableSize()` but empty.
if (suppress_build_) {
return true;
}
// Send the temporary table to the renderer processes via `listener_`
if (mapped_table_memory_.region.IsValid()) {
listener_->NewTable(&mapped_table_memory_.region);
}
#ifndef NDEBUG
DebugValidate();
#endif
return BuildTableFromDelegate();
}
// Initializes the shared memory structure.
bool PartitionedVisitedLinkWriter::CreateVisitedLinkTable(int32_t num_entries) {
base::MappedReadOnlyRegion table_memory;
if (!PartitionedVisitedLinkWriter::fail_table_creation_for_testing_ &&
CreateVisitedLinkTableHelper(num_entries, &table_memory)) {
mapped_table_memory_ = std::move(table_memory);
hash_table_ = GetHashTableFromMapping(mapped_table_memory_.mapping);
table_length_ = num_entries;
used_items_ = 0;
return true;
}
return false;
}
// static
bool PartitionedVisitedLinkWriter::CreateVisitedLinkTableHelper(
int32_t num_entries,
base::MappedReadOnlyRegion* memory) {
DCHECK(memory);
// The hashtable is a shared header followed by the entries.
uint32_t alloc_size =
num_entries * sizeof(Fingerprint) + sizeof(PartitionedSharedHeader);
base::UmaHistogramCustomCounts(
"History.VisitedLinks.HashTableSizeOnTableCreate",
alloc_size / 1024 / 1024, 1, 10000, 100);
// Create the shared memory object.
*memory = base::ReadOnlySharedMemoryRegion::Create(alloc_size);
if (!memory->IsValid()) {
return false;
}
UNSAFE_TODO(memset(memory->mapping.memory(), 0, alloc_size));
// Save the header for other processes to read.
PartitionedSharedHeader* header =
static_cast<PartitionedSharedHeader*>(memory->mapping.memory());
header->length = num_entries;
return true;
}
bool PartitionedVisitedLinkWriter::ResizeTableIfNecessary() {
DCHECK(table_length_ > 0) << "Must have a table";
// Load limits for good performance/space. We are pretty conservative about
// keeping the table not very full. This is because we use linear probing
// which increases the likelihood of clumps of entries which will reduce
// performance.
const float max_table_load = 0.5f; // Grow when we're > this full.
const float min_table_load = 0.2f; // Shrink when we're < this full.
float load = ComputeTableLoad();
if (load < max_table_load &&
(table_length_ <= static_cast<float>(kDefaultTableSize) ||
load > min_table_load)) {
return false;
}
// Table needs to grow or shrink.
int new_size = NewTableSizeForCount(used_items_);
DCHECK(new_size > used_items_);
DCHECK(load <= min_table_load || new_size > table_length_);
ResizeTable(new_size);
return true;
}
void PartitionedVisitedLinkWriter::ResizeTable(int32_t new_size) {
DCHECK(mapped_table_memory_.region.IsValid() &&
mapped_table_memory_.mapping.IsValid());
#ifndef NDEBUG
DebugValidate();
#endif
auto old_hash_table_mapping = std::move(mapped_table_memory_.mapping);
int32_t old_table_length = table_length_;
if (!CreateVisitedLinkTable(new_size)) {
// Restore modified members.
mapped_table_memory_.mapping = std::move(old_hash_table_mapping);
return;
}
{
Fingerprint* old_hash_table =
GetHashTableFromMapping(old_hash_table_mapping);
// Now we have two tables, our local copy which is the old one, and the new
// one loaded into this object where we need to copy the data.
for (int32_t i = 0; i < old_table_length; i++) {
Fingerprint cur = UNSAFE_TODO(old_hash_table[i]);
if (cur) {
AddFingerprint(cur, false);
}
}
}
// Send an update notification to all child processes so they read the new
// table.
listener_->NewTable(&mapped_table_memory_.region);
#ifndef NDEBUG
DebugValidate();
#endif
}
// See the TableBuilder definition in the header file for how this works.
bool PartitionedVisitedLinkWriter::BuildTableFromDelegate() {
DCHECK(!table_builder_);
table_builder_ =
base::MakeRefCounted<TableBuilder>(weak_ptr_factory_.GetWeakPtr());
delegate_->BuildVisitedLinkTable(table_builder_);
return true;
}
// NOTE: Keep VisitedLinkCommon::IsVisited in sync with this algorithm!
VisitedLinkWriter::Hash PartitionedVisitedLinkWriter::AddFingerprint(
Fingerprint fingerprint,
bool send_notifications) {
if (!hash_table_ || table_length_ == 0) {
base::UmaHistogramEnumeration("History.VisitedLinks.TryToAddFingerprint",
AddFingerprint::kTableError);
NOTREACHED(); // Not initialized.
}
Hash cur_hash = HashFingerprint(fingerprint);
Hash first_hash = cur_hash;
while (true) {
Fingerprint cur_fingerprint = FingerprintAt(cur_hash);
if (cur_fingerprint == fingerprint) {
base::UmaHistogramEnumeration("History.VisitedLinks.TryToAddFingerprint",
AddFingerprint::kAlreadyVisited);
return null_hash_; // This fingerprint is already in there, do nothing.
}
if (cur_fingerprint == null_fingerprint_) {
// End of probe sequence found, insert here.
UNSAFE_TODO(hash_table_[cur_hash]) = fingerprint;
used_items_++;
// If allowed, notify listener that a new visited link was added.
if (send_notifications) {
base::UmaHistogramEnumeration(
"History.VisitedLinks.TryToAddFingerprint",
AddFingerprint::kNewVisit);
listener_->Add(fingerprint);
}
return cur_hash;
}
// Advance in the probe sequence.
cur_hash = IncrementHash(cur_hash);
if (cur_hash == first_hash) {
// This means that we've wrapped around and are about to go into an
// infinite loop. Something was wrong with the hashtable resizing
// logic, so stop here.
base::UmaHistogramEnumeration("History.VisitedLinks.TryToAddFingerprint",
AddFingerprint::kTableError);
NOTREACHED();
}
}
}
void PartitionedVisitedLinkWriter::DeleteFingerprintsFromCurrentTable(
const std::set<Fingerprint>& fingerprints) {
// Delete the Fingerprints from the table.
for (auto i = fingerprints.begin(); i != fingerprints.end(); ++i) {
DeleteFingerprint(*i);
}
// These deleted fingerprints may make us shrink the table.
ResizeTableIfNecessary();
}
bool PartitionedVisitedLinkWriter::DeleteFingerprint(Fingerprint fingerprint) {
if (!hash_table_ || table_length_ == 0) {
NOTREACHED(); // Not initialized.
}
if (!IsVisited(fingerprint)) {
return false; // Not in the database to delete.
}
// First update the header used count.
used_items_--;
Hash deleted_hash = HashFingerprint(fingerprint);
// Find the range of "stuff" in the hash table that is adjacent to this
// fingerprint. These are things that could be affected by the change in
// the hash table. Since we use linear probing, anything after the deleted
// item up until an empty item could be affected.
Hash end_range = deleted_hash;
while (true) {
Hash next_hash = IncrementHash(end_range);
if (next_hash == deleted_hash) {
break; // We wrapped around and the whole table is full.
}
if (!UNSAFE_TODO(hash_table_[next_hash])) {
break; // Found the last spot.
}
end_range = next_hash;
}
// We could get all fancy and move the affected fingerprints around, but
// instead we just remove them all and re-add them (minus our deleted one).
// This will mean there's a small window of time where the affected links
// won't be marked visited.
absl::InlinedVector<Fingerprint, 32> shuffled_fingerprints;
Hash stop_loop = IncrementHash(end_range); // The end range is inclusive.
for (Hash i = deleted_hash; i != stop_loop; i = IncrementHash(i)) {
if (UNSAFE_TODO(hash_table_[i]) != fingerprint) {
// Don't save the one we're deleting!
shuffled_fingerprints.push_back(UNSAFE_TODO(hash_table_[i]));
// This will balance the increment of this value in AddFingerprint below
// so there is no net change.
used_items_--;
}
UNSAFE_TODO(hash_table_[i]) = null_fingerprint_;
}
if (!shuffled_fingerprints.empty()) {
// Need to add the new items back.
for (size_t i = 0; i < shuffled_fingerprints.size(); i++) {
AddFingerprint(shuffled_fingerprints[i], false);
}
}
return true;
}
// See the TableBuilder declaration above for how this works.
void PartitionedVisitedLinkWriter::OnTableBuildComplete(
bool success,
const std::vector<Fingerprint>& fingerprints,
std::map<url::Origin, uint64_t> salts) {
table_builder_ = nullptr; // Will release our reference to the builder.
if (success) {
// Replace salts_ with the map created when we built the hashtable on the DB
// thread.
salts_ = std::move(salts);
// Generate space for the new table in shared memory.
int new_table_size = NewTableSizeForCount(
static_cast<int>(fingerprints.size() + added_during_build_.size()));
if (CreateVisitedLinkTable(new_table_size)) {
// Add the stored fingerprints to the hash table.
for (const auto& fingerprint : fingerprints) {
AddFingerprint(fingerprint, false);
}
// Also add anything that was added while we were asynchronously
// generating the new table.
for (const auto& link : added_during_build_) {
CHECK(link.IsValid());
const std::optional<uint64_t> salt =
GetOrAddOriginSalt(link.frame_origin);
CHECK(salt.has_value());
AddFingerprint(ComputePartitionedFingerprint(link, salt.value()),
false);
}
added_during_build_.clear();
// Now handle deletions. Do not shrink the table now, we'll shrink it when
// adding or deleting a visited link the next time.
for (const auto& link : deleted_during_build_) {
CHECK(link.IsValid());
const std::optional<uint64_t> salt =
GetOrAddOriginSalt(link.frame_origin);
CHECK(salt.has_value());
DeleteFingerprint(ComputePartitionedFingerprint(link, salt.value()));
}
deleted_during_build_.clear();
// Send an update notification to all child processes.
listener_->NewTable(&mapped_table_memory_.region);
// All tabs which was loaded when table was being rebuilt
// invalidate their links again.
listener_->Reset(false);
}
}
// Now that we have completed our build on the DB thread, we can recover the
// per-origin salts of navigations that took place during the build.
listener_->UpdateOriginSalts();
// Notify the unit test that the build is complete (will be NULL in prod.)
if (!build_complete_task_.is_null()) {
std::move(build_complete_task_).Run();
}
}
uint32_t PartitionedVisitedLinkWriter::DefaultTableSize() const {
if (table_size_override_) {
return table_size_override_;
}
return kDefaultTableSize;
}
uint32_t PartitionedVisitedLinkWriter::NewTableSizeForCount(
int32_t item_count) const {
// These table sizes are selected to be the maximum prime number less than
// a "convenient" multiple of 1K.
static const auto table_sizes = std::to_array<int>({
16381, // 16K = 16384 <- don't shrink below this table size
// (should be == default_table_size)
32767, // 32K = 32768
65521, // 64K = 65536
130051, // 128K = 131072
262127, // 256K = 262144
524269, // 512K = 524288
1048549, // 1M = 1048576
2097143, // 2M = 2097152
4194301, // 4M = 4194304
8388571, // 8M = 8388608
16777199, // 16M = 16777216
33554347,
}); // 32M = 33554432
// Try to leave the table 33% full.
int desired = item_count * 3;
// Find the closest prime.
for (size_t i = 0; i < std::size(table_sizes); i++) {
if (table_sizes[i] > desired) {
return table_sizes[i];
}
}
// Growing very big, just approximate a "good" number, not growing as much
// as normal.
return item_count * 2 - 1;
}
void PartitionedVisitedLinkWriter::AddVisitedLink(const VisitedLink& link) {
TRACE_EVENT0("browser", "PartitionedVisitedLinkWriter::AddVisitedLink");
base::UmaHistogramCounts10M("History.VisitedLinks.HashTableUsageOnLinkAdded",
used_items_);
// Attempt to add the visited link to the in-memory partitioned hashtable and
// record whether we returned a valid hash index.
bool did_add_link = (TryToAddVisitedLink(link) != null_hash_);
// When kPartitionVisitedLinkDatabaseWithSelfLinks is enabled, we attempt to
// add <link_url, link_url, link_url> to the in-memory partitioned hashtable
// as well.
bool did_add_self_link = false;
if (base::FeatureList::IsEnabled(
blink::features::kPartitionVisitedLinkDatabaseWithSelfLinks)) {
std::optional<VisitedLink> self_link = link.MaybeCreateSelfLink();
if (self_link.has_value()) {
// Attempt to add the self-link and record whether we returned a valid
// hash index.
did_add_self_link =
(TryToAddVisitedLink(self_link.value()) != null_hash_);
}
}
// If we have added a link and/or a self-link, we need to call
// ResizeTableIfNecessary() to determine whether we need to increase the
// available space in the partitioned hashtable. Before doing so, we also
// check that the table isn't currently building, which would make this
// operation redundant.
if (!table_builder_ && (did_add_link || did_add_self_link)) {
ResizeTableIfNecessary();
}
}
VisitedLinkWriter::Hash PartitionedVisitedLinkWriter::TryToAddVisitedLink(
const VisitedLink& link) {
// Extra check that we are not incognito. This should not happen.
// TODO(boliu): Move this check to HistoryService when IsOffTheRecord is
// removed from BrowserContext.
if (browser_context_ && browser_context_->IsOffTheRecord()) {
NOTREACHED();
}
// We don't want to add any invalid VisitedLinks to the hashtable.
if (!link.IsValid()) {
return null_hash_;
}
// If the table isn't finished building, accumulated links will be
// applied to the table.
if (table_builder_.get()) {
// If we have a pending delete for this link, cancel it.
deleted_during_build_.erase(link);
// A build is in progress, save this addition in the temporary
// list so it can be added once build is complete.
added_during_build_.insert(link);
}
const std::optional<uint64_t> salt = GetOrAddOriginSalt(link.frame_origin);
if (!salt.has_value()) {
return null_hash_;
}
Fingerprint fingerprint = ComputePartitionedFingerprint(link, salt.value());
// If the table is "full", we don't add URLs and just drop them on the floor.
// This can happen if we get thousands of new URLs and something causes
// the table resizing to fail. This check prevents a hang in that case. Note
// that this is *not* the resize limit, this is just a sanity check.
if (used_items_ / 8 > table_length_ / 10) {
return null_hash_; // Table is more than 80% full.
}
return AddFingerprint(fingerprint, true);
}
void PartitionedVisitedLinkWriter::DeleteAllVisitedLinks() {
// Any pending modifications are invalid.
added_during_build_.clear();
deleted_during_build_.clear();
// Clear the hash table.
used_items_ = 0;
UNSAFE_TODO(
memset(hash_table_, 0, this->table_length_ * sizeof(Fingerprint)));
// Resize it if it is now too empty. Resize may write the new table out for
// us, otherwise, schedule writing the new table to disk ourselves.
ResizeTableIfNecessary();
// Notify reader instances that hashtable state has changed.
listener_->Reset(false);
}
void PartitionedVisitedLinkWriter::DeleteVisitedLinks(
VisitedLinkIterator* links) {
if (!links->HasNextVisitedLink()) {
return;
}
// Cache the feature status to avoid frequent calculation.
const bool are_self_links_enabled = base::FeatureList::IsEnabled(
blink::features::kPartitionVisitedLinkDatabaseWithSelfLinks);
// Notify reader instances that hashtable state has changed.
listener_->Reset(false);
if (table_builder_.get()) {
// A build is in progress, save this deletion in the temporary
// list so it can be deleted once the build is complete.
while (links->HasNextVisitedLink()) {
// Obtain the next link we want to delete from the hashtable.
const VisitedLink& link(links->NextVisitedLink());
if (!link.IsValid()) {
continue;
}
deleted_during_build_.insert(link);
// If the VisitedLink was just added and now we're deleting it, it may be
// in the list of things added since the last build. Delete it from that
// list.
added_during_build_.erase(link);
// If self-links are enabled, we have added links to the in-memory
// partitioned hashtable that do not exist in the VisitedLinkDatabase. As
// a result, we must construct the self-link counterpart to each of these
// VisitedLinks deleted from the VisitedLinkDatabase, so that both the
// link and self-link are removed from the partitioned hashtable.
if (are_self_links_enabled) {
std::optional<VisitedLink> self_link = link.MaybeCreateSelfLink();
if (self_link.has_value()) {
deleted_during_build_.insert(self_link.value());
added_during_build_.erase(self_link.value());
}
}
}
return;
}
// Compute the deleted URLs' fingerprints and delete them.
std::set<Fingerprint> deleted_fingerprints;
while (links->HasNextVisitedLink()) {
const VisitedLink& link(links->NextVisitedLink());
if (!link.IsValid()) {
continue;
}
const std::optional<uint64_t> salt = GetOrAddOriginSalt(link.frame_origin);
if (!salt.has_value()) {
continue;
}
deleted_fingerprints.insert(
ComputePartitionedFingerprint(link, salt.value()));
// If self-links are enabled, we have added links to the in-memory
// partitioned hashtable that do not exist in the VisitedLinkDatabase. As
// a result, we must construct the self-link counterpart to each of these
// VisitedLinks deleted from the VisitedLinkDatabase, so that both the
// link and self-link are removed from the partitioned hashtable.
if (are_self_links_enabled) {
std::optional<VisitedLink> self_link = link.MaybeCreateSelfLink();
if (self_link.has_value()) {
const std::optional<uint64_t> self_salt =
GetOrAddOriginSalt(self_link->frame_origin);
if (!self_salt.has_value()) {
continue;
}
deleted_fingerprints.insert(ComputePartitionedFingerprint(
self_link.value(), self_salt.value()));
}
}
}
DeleteFingerprintsFromCurrentTable(deleted_fingerprints);
}
std::optional<uint64_t> PartitionedVisitedLinkWriter::GetOrAddOriginSalt(
const url::Origin& origin) {
// To avoid race conditions, we should not get from or add to the salt map
// while the hashtable is building.
// TODO(crbug.com/332364003): implement a new VisitedLinkNotificationSink
// interface to determine and send salts for navigations that take place while
// the hashtable is building.
if (table_builder_) {
return std::nullopt;
}
// Obtain the salt for this origin if it already exists.
auto it = salts_.find(origin);
if (it != salts_.end()) {
return it->second;
}
// Otherwise, generate a new salt for this origin.
const uint64_t generated_salt = base::RandUint64();
salts_.insert({origin, generated_salt});
return generated_salt;
}
// static
VisitedLinkCommon::Fingerprint*
PartitionedVisitedLinkWriter::GetHashTableFromMapping(
base::WritableSharedMemoryMapping& hash_table_mapping) {
DCHECK(hash_table_mapping.IsValid());
// Our table pointer is just the data immediately following the header.
return reinterpret_cast<Fingerprint*>(
UNSAFE_TODO(static_cast<char*>(hash_table_mapping.memory()) +
sizeof(PartitionedSharedHeader)));
}
} // namespace visitedlink