| // Copyright 2020 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 "content/browser/conversions/conversion_manager_impl.h" |
| |
| #include <utility> |
| |
| #include "base/barrier_closure.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/task_runner_util.h" |
| #include "base/time/default_clock.h" |
| #include "content/browser/conversions/conversion_reporter_impl.h" |
| #include "content/browser/conversions/conversion_storage_delegate_impl.h" |
| #include "content/browser/conversions/conversion_storage_sql.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/content_switches.h" |
| |
| namespace content { |
| |
| const constexpr base::TimeDelta kConversionManagerQueueReportsInterval = |
| base::TimeDelta::FromMinutes(30); |
| |
| ConversionManager* ConversionManagerProviderImpl::GetManager( |
| WebContents* web_contents) const { |
| return static_cast<StoragePartitionImpl*>( |
| BrowserContext::GetDefaultStoragePartition( |
| web_contents->GetBrowserContext())) |
| ->GetConversionManager(); |
| } |
| |
| // static |
| void ConversionManagerImpl::RunInMemoryForTesting() { |
| ConversionStorageSql::RunInMemoryForTesting(); |
| } |
| |
| // static |
| std::unique_ptr<ConversionManagerImpl> ConversionManagerImpl::CreateForTesting( |
| std::unique_ptr<ConversionReporter> reporter, |
| std::unique_ptr<ConversionPolicy> policy, |
| const base::Clock* clock, |
| const base::FilePath& user_data_directory, |
| scoped_refptr<base::SequencedTaskRunner> storage_task_runner) { |
| return base::WrapUnique<ConversionManagerImpl>(new ConversionManagerImpl( |
| std::move(reporter), std::move(policy), clock, user_data_directory, |
| std::move(storage_task_runner))); |
| } |
| |
| ConversionManagerImpl::ConversionManagerImpl( |
| StoragePartition* storage_partition, |
| const base::FilePath& user_data_directory, |
| scoped_refptr<base::SequencedTaskRunner> task_runner) |
| : ConversionManagerImpl( |
| std::make_unique<ConversionReporterImpl>( |
| storage_partition, |
| base::DefaultClock::GetInstance()), |
| std::make_unique<ConversionPolicy>( |
| base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kConversionsDebugMode)), |
| base::DefaultClock::GetInstance(), |
| user_data_directory, |
| std::move(task_runner)) {} |
| |
| ConversionManagerImpl::ConversionManagerImpl( |
| std::unique_ptr<ConversionReporter> reporter, |
| std::unique_ptr<ConversionPolicy> policy, |
| const base::Clock* clock, |
| const base::FilePath& user_data_directory, |
| scoped_refptr<base::SequencedTaskRunner> storage_task_runner) |
| : debug_mode_(base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kConversionsDebugMode)), |
| storage_task_runner_(std::move(storage_task_runner)), |
| clock_(clock), |
| reporter_(std::move(reporter)), |
| storage_(new ConversionStorageSql( |
| user_data_directory, |
| std::make_unique<ConversionStorageDelegateImpl>(debug_mode_), |
| clock_), |
| base::OnTaskRunnerDeleter(storage_task_runner_)), |
| conversion_policy_(std::move(policy)), |
| weak_factory_(this) { |
| // Unretained is safe because any task to delete |storage_| will be posted |
| // after this one. |
| base::PostTaskAndReplyWithResult( |
| storage_task_runner_.get(), FROM_HERE, |
| base::BindOnce(&ConversionStorage::Initialize, |
| base::Unretained(storage_.get())), |
| base::BindOnce(&ConversionManagerImpl::OnInitCompleted, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| ConversionManagerImpl::~ConversionManagerImpl() = default; |
| |
| void ConversionManagerImpl::HandleImpression( |
| const StorableImpression& impression) { |
| // Add the impression to storage. |
| storage_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&ConversionStorage::StoreImpression, |
| base::Unretained(storage_.get()), impression)); |
| } |
| |
| void ConversionManagerImpl::HandleConversion( |
| const StorableConversion& conversion) { |
| // TODO(https://crbug.com/1043345): Add UMA for the number of conversions we |
| // are logging to storage, and the number of new reports logged to storage. |
| // Unretained is safe because any task to delete |storage_| will be posted |
| // after this one. |
| storage_task_runner_.get()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| base::IgnoreResult( |
| &ConversionStorage::MaybeCreateAndStoreConversionReports), |
| base::Unretained(storage_.get()), conversion)); |
| |
| // If we are running in debug mode, we should also schedule a task to |
| // gather and send any new reports. |
| if (debug_mode_) |
| GetAndQueueReportsForNextInterval(); |
| } |
| |
| void ConversionManagerImpl::GetActiveImpressionsForWebUI( |
| base::OnceCallback<void(std::vector<StorableImpression>)> callback) { |
| // Unretained is safe because any task to delete |storage_| will be posted |
| // after this one because |storage_| uses base::OnTaskRunnerDeleter. |
| base::PostTaskAndReplyWithResult( |
| storage_task_runner_.get(), FROM_HERE, |
| base::BindOnce(&ConversionStorage::GetActiveImpressions, |
| base::Unretained(storage_.get())), |
| std::move(callback)); |
| } |
| |
| void ConversionManagerImpl::GetReportsForWebUI( |
| base::OnceCallback<void(std::vector<ConversionReport>)> callback, |
| base::Time max_report_time) { |
| GetAndHandleReports(std::move(callback), max_report_time); |
| } |
| |
| void ConversionManagerImpl::SendReportsForWebUI(base::OnceClosure done) { |
| GetAndHandleReports( |
| base::BindOnce(&ConversionManagerImpl::HandleReportsSentFromWebUI, |
| weak_factory_.GetWeakPtr(), std::move(done)), |
| base::Time::Max()); |
| } |
| |
| const ConversionPolicy& ConversionManagerImpl::GetConversionPolicy() const { |
| return *conversion_policy_; |
| } |
| |
| void ConversionManagerImpl::ClearData( |
| base::Time delete_begin, |
| base::Time delete_end, |
| base::RepeatingCallback<bool(const url::Origin&)> filter, |
| base::OnceClosure done) { |
| storage_task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&ConversionStorage::ClearData, |
| base::Unretained(storage_.get()), delete_begin, delete_end, |
| std::move(filter)), |
| std::move(done)); |
| } |
| |
| void ConversionManagerImpl::OnInitCompleted(bool success) { |
| // The storage layer is robust to initialization failures, so ignore the case |
| // where it failed to setup. |
| if (!success) { |
| // TODO(https://crbug.com/1099812): Log metrics on storage initialization |
| // success. |
| return; |
| } |
| |
| // Once the database is loaded, get all reports that may have expired while |
| // Chrome was not running and handle these specially. |
| GetAndHandleReports( |
| base::BindOnce(&ConversionManagerImpl::HandleReportsExpiredAtStartup, |
| weak_factory_.GetWeakPtr()), |
| clock_->Now() + kConversionManagerQueueReportsInterval); |
| |
| // Start a repeating timer that will fetch reports once every |
| // |kConversionManagerQueueReportsInterval| and add them to |reporter_|. |
| get_and_queue_reports_timer_.Start( |
| FROM_HERE, kConversionManagerQueueReportsInterval, this, |
| &ConversionManagerImpl::GetAndQueueReportsForNextInterval); |
| } |
| |
| void ConversionManagerImpl::GetAndHandleReports( |
| ReportsHandlerFunc handler_function, |
| base::Time max_report_time) { |
| base::PostTaskAndReplyWithResult( |
| storage_task_runner_.get(), FROM_HERE, |
| base::BindOnce(&ConversionStorage::GetConversionsToReport, |
| base::Unretained(storage_.get()), max_report_time), |
| std::move(handler_function)); |
| } |
| |
| void ConversionManagerImpl::GetAndQueueReportsForNextInterval() { |
| // Get all the reports that will be reported in the next interval and them to |
| // the |reporter_|. |
| GetAndHandleReports(base::BindOnce(&ConversionManagerImpl::QueueReports, |
| weak_factory_.GetWeakPtr()), |
| clock_->Now() + kConversionManagerQueueReportsInterval); |
| } |
| |
| void ConversionManagerImpl::QueueReports( |
| std::vector<ConversionReport> reports) { |
| if (!reports.empty()) { |
| // |reporter_| is owned by |this|, so base::Unretained() is safe as the |
| // reporter and callbacks will be deleted first. |
| reporter_->AddReportsToQueue( |
| std::move(reports), |
| base::BindRepeating(&ConversionManagerImpl::OnReportSent, |
| base::Unretained(this))); |
| } |
| } |
| |
| void ConversionManagerImpl::HandleReportsExpiredAtStartup( |
| std::vector<ConversionReport> reports) { |
| // Add delay to all reports that expired while the browser was not running so |
| // they are not temporally join-able. |
| base::Time current_time = clock_->Now(); |
| for (ConversionReport& report : reports) { |
| if (report.report_time > current_time) |
| continue; |
| |
| base::Time updated_report_time = |
| conversion_policy_->GetReportTimeForExpiredReportAtStartup( |
| current_time); |
| |
| report.extra_delay = updated_report_time - report.report_time; |
| report.report_time = updated_report_time; |
| } |
| QueueReports(std::move(reports)); |
| } |
| |
| void ConversionManagerImpl::HandleReportsSentFromWebUI( |
| base::OnceClosure done, |
| std::vector<ConversionReport> reports) { |
| if (reports.empty()) { |
| std::move(done).Run(); |
| return; |
| } |
| |
| // All reports should be sent immediately. |
| for (ConversionReport& report : reports) { |
| report.report_time = base::Time::Min(); |
| } |
| |
| // Wraps |done| so that is will run once all of the reports have finished |
| // sending. |
| base::RepeatingClosure all_reports_sent = |
| base::BarrierClosure(reports.size(), std::move(done)); |
| |
| // |reporter_| is owned by |this|, so base::Unretained() is safe as the |
| // reporter and callbacks will be deleted first. |
| reporter_->AddReportsToQueue( |
| std::move(reports), |
| base::BindRepeating(&ConversionManagerImpl::OnReportSentFromWebUI, |
| base::Unretained(this), std::move(all_reports_sent))); |
| } |
| |
| void ConversionManagerImpl::OnReportSent(int64_t conversion_id) { |
| storage_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&ConversionStorage::DeleteConversion), |
| base::Unretained(storage_.get()), conversion_id)); |
| } |
| |
| void ConversionManagerImpl::OnReportSentFromWebUI( |
| base::OnceClosure reports_sent_barrier, |
| int64_t conversion_id) { |
| // |reports_sent_barrier| is a OnceClosure view of a RepeatingClosure obtained |
| // by base::BarrierClosure(). |
| storage_task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(base::IgnoreResult(&ConversionStorage::DeleteConversion), |
| base::Unretained(storage_.get()), conversion_id), |
| std::move(reports_sent_barrier)); |
| } |
| |
| } // namespace content |