blob: c8ee1eddcd6f9da94a570b287747dc2ab7e5b441 [file] [log] [blame]
// Copyright 2012 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/omnibox/browser/in_memory_url_index.h"
#include <inttypes.h>
#include <cinttypes>
#include <memory>
#include "base/feature_list.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/strings/stringprintf.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_usage_estimator.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/url_database.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/omnibox/browser/omnibox_triggered_feature_service.h"
#include "components/omnibox/browser/url_index_private_data.h"
#include "components/omnibox/common/omnibox_features.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
// Initializes a allowlist of URL schemes.
void InitializeSchemeAllowlist(SchemeSet* allowlist,
const SchemeSet& client_schemes_to_allowlist) {
DCHECK(allowlist);
if (!allowlist->empty())
return; // Nothing to do, already initialized.
allowlist->insert(client_schemes_to_allowlist.begin(),
client_schemes_to_allowlist.end());
allowlist->insert(std::string(url::kAboutScheme));
allowlist->insert(std::string(url::kFileScheme));
allowlist->insert(std::string(url::kFtpScheme));
allowlist->insert(std::string(url::kHttpScheme));
allowlist->insert(std::string(url::kHttpsScheme));
allowlist->insert(std::string(url::kMailToScheme));
}
// RebuildPrivateDataFromHistoryDBTask -----------------------------------------
InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
RebuildPrivateDataFromHistoryDBTask(base::WeakPtr<InMemoryURLIndex> index,
const SchemeSet& scheme_allowlist)
: index_(index), scheme_allowlist_(scheme_allowlist) {}
bool InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::RunOnDBThread(
history::HistoryBackend* backend,
history::HistoryDatabase* db) {
data_ = URLIndexPrivateData::RebuildFromHistory(db, scheme_allowlist_);
succeeded_ = data_.get() && !data_->Empty();
if (!succeeded_ && data_.get())
data_->Clear();
return true;
}
void InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
DoneRunOnMainThread() {
if (index_) {
index_->DoneRebuildingPrivateDataFromHistoryDB(succeeded_, data_);
}
}
InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask::
~RebuildPrivateDataFromHistoryDBTask() = default;
// InMemoryURLIndex ------------------------------------------------------------
InMemoryURLIndex::InMemoryURLIndex(bookmarks::BookmarkModel* bookmark_model,
history::HistoryService* history_service,
TemplateURLService* template_url_service,
const base::FilePath& history_dir,
const SchemeSet& client_schemes_to_allowlist)
: bookmark_model_(bookmark_model),
history_service_(history_service),
template_url_service_(template_url_service),
private_data_(new URLIndexPrivateData),
task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT})) {
InitializeSchemeAllowlist(&scheme_allowlist_, client_schemes_to_allowlist);
// TODO(mrossetti): Register for language change notifications.
if (history_service_)
history_service_observation_.Observe(history_service_.get());
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
this, "InMemoryURLIndex",
base::SingleThreadTaskRunner::GetCurrentDefault());
}
InMemoryURLIndex::~InMemoryURLIndex() {
base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
this);
DCHECK(!history_service_);
DCHECK(shutdown_);
}
void InMemoryURLIndex::Init() {
TRACE_EVENT_BEGIN("omnibox", "InMemoryURLIndex::Init",
perfetto::Track::FromPointer(this));
if (!history_service_)
return;
// If the HistoryService backend is not initialized yet, that's okay. We're
// scheduled to process our task once it's initialized.
history_service_->ScheduleDBTask(
FROM_HERE,
std::make_unique<InMemoryURLIndex::RebuildPrivateDataFromHistoryDBTask>(
weak_ptr_factory_.GetWeakPtr(), scheme_allowlist_),
&private_data_tracker_);
}
void InMemoryURLIndex::ClearPrivateData() {
private_data_->Clear();
}
// Querying --------------------------------------------------------------------
ScoredHistoryMatches InMemoryURLIndex::HistoryItemsForTerms(
const std::u16string& term_string,
size_t cursor_position,
size_t max_matches,
OmniboxTriggeredFeatureService* triggered_feature_service) {
return private_data_->HistoryItemsForTerms(
term_string, cursor_position, max_matches, bookmark_model_,
template_url_service_, triggered_feature_service);
}
// Updating --------------------------------------------------------------------
void InMemoryURLIndex::DeleteURL(const GURL& url) {
private_data_->DeleteURL(url);
}
void InMemoryURLIndex::OnURLVisited(history::HistoryService* history_service,
const history::URLRow& url_row,
const history::VisitRow& new_visit) {
DCHECK_EQ(history_service_, history_service);
private_data_->UpdateURL(history_service_, url_row, scheme_allowlist_,
&private_data_tracker_);
}
void InMemoryURLIndex::OnURLsModified(history::HistoryService* history_service,
const history::URLRows& changed_urls) {
DCHECK_EQ(history_service_, history_service);
for (const auto& row : changed_urls) {
private_data_->UpdateURL(history_service_, row, scheme_allowlist_,
&private_data_tracker_);
}
}
void InMemoryURLIndex::OnHistoryDeletions(
history::HistoryService* history_service,
const history::DeletionInfo& deletion_info) {
if (deletion_info.IsAllHistory()) {
ClearPrivateData();
} else {
for (const auto& row : deletion_info.deleted_rows())
private_data_->DeleteURL(row.url());
}
}
bool InMemoryURLIndex::OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* process_memory_dump) {
size_t res = 0;
res += base::trace_event::EstimateMemoryUsage(scheme_allowlist_);
// TODO(dyaroshev): Add support for scoped_refptr in
// base::trace_event::EstimateMemoryUsage.
res += sizeof(URLIndexPrivateData) + private_data_->EstimateMemoryUsage();
const std::string dump_name =
base::StringPrintf("omnibox/in_memory_url_index/0x%" PRIXPTR,
reinterpret_cast<uintptr_t>(this));
auto* dump = process_memory_dump->CreateAllocatorDump(dump_name);
dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
base::trace_event::MemoryAllocatorDump::kUnitsBytes, res);
return true;
}
// Cleanup ---------------------------------------------------------------------
void InMemoryURLIndex::Shutdown() {
if (history_service_) {
history_service_observation_.Reset();
history_service_ = nullptr;
}
shutdown_ = true;
private_data_tracker_.TryCancelAll();
#if !defined(LEAK_SANITIZER) && !BUILDFLAG(IS_ANDROID)
// Intentionally create and then leak a scoped_refptr to private_data_. This
// permanently raises the reference count so that the URLIndexPrivateData
// destructor won't run during browser shutdown. This saves having to walk the
// maps to free their memory, which saves time and avoids shutdown hangs,
// especially if some of the memory has been paged out. Note that we only want
// to do this if the whole browser is shutting down. If it's just the Profile
// being destroyed, we don't want to leak the memory. Android doesn't have
// KeepAliveRegistry, and doesn't need this anyways, since it kills the
// process without shutdown whenever it needs to.
if (KeepAliveRegistry::GetInstance()->IsShuttingDown()) {
base::NoDestructor<scoped_refptr<URLIndexPrivateData>> leak_reference(
private_data_);
}
#endif // !defined(LEAK_SANITIZER) && !BUILDFLAG(IS_ANDROID)
}
// Restoring from the History DB -----------------------------------------------
void InMemoryURLIndex::DoneRebuildingPrivateDataFromHistoryDB(
bool succeeded,
scoped_refptr<URLIndexPrivateData> private_data) {
TRACE_EVENT_END(
"omnibox",
/* InMemoryURLIndex::Init */ perfetto::Track::FromPointer(this));
DCHECK(thread_checker_.CalledOnValidThread());
if (succeeded) {
private_data_tracker_.TryCancelAll();
private_data_ = private_data;
} else {
private_data_->Clear();
}
restored_ = true;
}