diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-12 14:27:29 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:35:20 +0000 |
commit | c30a6232df03e1efbd9f3b226777b07e087a1122 (patch) | |
tree | e992f45784689f373bcc38d1b79a239ebe17ee23 /chromium/content/browser/conversions | |
parent | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (diff) | |
download | qtwebengine-chromium-85-based.tar.gz |
BASELINE: Update Chromium to 85.0.4183.14085-based
Change-Id: Iaa42f4680837c57725b1344f108c0196741f6057
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/content/browser/conversions')
20 files changed, 657 insertions, 111 deletions
diff --git a/chromium/content/browser/conversions/conversion_host.cc b/chromium/content/browser/conversions/conversion_host.cc index 0a695271ca4..f20c780d2f8 100644 --- a/chromium/content/browser/conversions/conversion_host.cc +++ b/chromium/content/browser/conversions/conversion_host.cc @@ -17,8 +17,12 @@ #include "content/browser/frame_host/render_frame_host_impl.h" #include "content/browser/storage_partition_impl.h" #include "content/public/browser/browser_context.h" +#include "content/public/browser/content_browser_client.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/content_client.h" #include "mojo/public/cpp/bindings/message.h" #include "services/network/public/cpp/is_potentially_trustworthy.h" #include "third_party/blink/public/mojom/devtools/console_message.mojom.h" @@ -150,6 +154,11 @@ void ConversionHost::DidFinishNavigation(NavigationHandle* navigation_handle) { return; } + if (!GetContentClient()->browser()->AllowConversionMeasurement( + web_contents()->GetBrowserContext())) { + return; + } + // Convert |impression| into a StorableImpression that can be forwarded to // storage. If a reporting origin was not provided, default to the conversion // destination for reporting. @@ -208,6 +217,11 @@ void ConversionHost::RegisterConversion( return; } + if (!GetContentClient()->browser()->AllowConversionMeasurement( + web_contents()->GetBrowserContext())) { + return; + } + StorableConversion storable_conversion( conversion_manager->GetConversionPolicy().GetSanitizedConversionData( conversion->conversion_data), diff --git a/chromium/content/browser/conversions/conversion_host.h b/chromium/content/browser/conversions/conversion_host.h index 169c08a8042..8f004115658 100644 --- a/chromium/content/browser/conversions/conversion_host.h +++ b/chromium/content/browser/conversions/conversion_host.h @@ -46,6 +46,8 @@ class CONTENT_EXPORT ConversionHost : public WebContentsObserver, FRIEND_TEST_ALL_PREFIXES(ConversionHostTest, PerPageConversionMetrics); FRIEND_TEST_ALL_PREFIXES(ConversionHostTest, NoManager_NoPerPageConversionMetrics); + FRIEND_TEST_ALL_PREFIXES(ConversionHostTest, + ValidConversionWithEmbedderDisable_NoConversion); ConversionHost( WebContents* web_contents, diff --git a/chromium/content/browser/conversions/conversion_host_unittest.cc b/chromium/content/browser/conversions/conversion_host_unittest.cc index 2769ef893d8..644ff248b09 100644 --- a/chromium/content/browser/conversions/conversion_host_unittest.cc +++ b/chromium/content/browser/conversions/conversion_host_unittest.cc @@ -11,6 +11,7 @@ #include "content/browser/conversions/conversion_manager.h" #include "content/browser/conversions/conversion_test_utils.h" #include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/common/content_client.h" #include "content/public/common/content_features.h" #include "content/public/test/test_renderer_host.h" #include "content/test/fake_mojo_message_dispatch_context.h" @@ -149,6 +150,40 @@ TEST_F(ConversionHostTest, ValidConversion_NoBadMessage) { EXPECT_EQ(1u, test_manager_.num_conversions()); } +TEST_F(ConversionHostTest, ValidConversionWithEmbedderDisable_NoConversion) { + ConversionDisallowingContentBrowserClient disallowed_browser_client; + ContentBrowserClient* old_browser_client = + SetBrowserClientForTesting(&disallowed_browser_client); + + // Create a page with a secure origin. + contents()->NavigateAndCommit(GURL("https://www.example.com")); + conversion_host()->SetCurrentTargetFrameForTesting(main_rfh()); + + blink::mojom::ConversionPtr conversion = blink::mojom::Conversion::New(); + conversion->reporting_origin = + url::Origin::Create(GURL("https://secure.com")); + conversion_host()->RegisterConversion(std::move(conversion)); + + EXPECT_EQ(0u, test_manager_.num_conversions()); + SetBrowserClientForTesting(old_browser_client); +} + +TEST_F(ConversionHostTest, ValidImpressionWithEmbedderDisable_NoImpression) { + ConversionDisallowingContentBrowserClient disallowed_browser_client; + ContentBrowserClient* old_browser_client = + SetBrowserClientForTesting(&disallowed_browser_client); + + contents()->NavigateAndCommit(GURL("https://secure_impression.com")); + auto navigation = NavigationSimulatorImpl::CreateRendererInitiated( + GURL(kConversionUrl), main_rfh()); + navigation->SetInitiatorFrame(main_rfh()); + navigation->set_impression(CreateValidImpression()); + navigation->Commit(); + + EXPECT_EQ(0u, test_manager_.num_impressions()); + SetBrowserClientForTesting(old_browser_client); +} + TEST_F(ConversionHostTest, PerPageConversionMetrics) { base::HistogramTester histograms; diff --git a/chromium/content/browser/conversions/conversion_manager_impl.cc b/chromium/content/browser/conversions/conversion_manager_impl.cc index 4f3f9ca7e5a..7ef0173fbd5 100644 --- a/chromium/content/browser/conversions/conversion_manager_impl.cc +++ b/chromium/content/browser/conversions/conversion_manager_impl.cc @@ -35,6 +35,11 @@ ConversionManager* ConversionManagerProviderImpl::GetManager( } // static +void ConversionManagerImpl::RunInMemoryForTesting() { + ConversionStorageSql::RunInMemoryForTesting(); +} + +// static std::unique_ptr<ConversionManagerImpl> ConversionManagerImpl::CreateForTesting( std::unique_ptr<ConversionReporter> reporter, std::unique_ptr<ConversionPolicy> policy, @@ -93,9 +98,6 @@ ConversionManagerImpl::~ConversionManagerImpl() = default; void ConversionManagerImpl::HandleImpression( const StorableImpression& impression) { - if (!storage_) - return; - // Add the impression to storage. storage_task_runner_->PostTask( FROM_HERE, base::BindOnce(&ConversionStorage::StoreImpression, @@ -104,9 +106,6 @@ void ConversionManagerImpl::HandleImpression( void ConversionManagerImpl::HandleConversion( const StorableConversion& conversion) { - if (!storage_) - return; - // 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 @@ -166,8 +165,11 @@ void ConversionManagerImpl::ClearData( } void ConversionManagerImpl::OnInitCompleted(bool success) { + // The storage layer is robust to initialization failures, so ignore the case + // where it failed to setup. if (!success) { - storage_.reset(); + // TODO(https://crbug.com/1099812): Log metrics on storage initialization + // success. return; } diff --git a/chromium/content/browser/conversions/conversion_manager_impl.h b/chromium/content/browser/conversions/conversion_manager_impl.h index 03bcd94688f..a7357b5bb98 100644 --- a/chromium/content/browser/conversions/conversion_manager_impl.h +++ b/chromium/content/browser/conversions/conversion_manager_impl.h @@ -64,6 +64,10 @@ class CONTENT_EXPORT ConversionManagerImpl : public ConversionManager { base::RepeatingCallback<void(int64_t)> report_sent_callback) = 0; }; + // Configures underlying storage to be setup in memory, rather than on + // disk. This speeds up initialization to avoid timeouts in test environments. + static void RunInMemoryForTesting(); + static std::unique_ptr<ConversionManagerImpl> CreateForTesting( std::unique_ptr<ConversionReporter> reporter, std::unique_ptr<ConversionPolicy> policy, @@ -72,6 +76,9 @@ class CONTENT_EXPORT ConversionManagerImpl : public ConversionManager { scoped_refptr<base::SequencedTaskRunner> storage_task_runner); // |storage_task_runner| should run with base::TaskPriority::BEST_EFFORT. + // TODO(https://crbug.com/1080764): The storage task runner is instead run + // with base::TaskPriority::USER_VISIBLE to address some timeouts. + // Documentation should be updated. ConversionManagerImpl( StoragePartition* storage_partition, const base::FilePath& user_data_directory, @@ -162,8 +169,7 @@ class CONTENT_EXPORT ConversionManagerImpl : public ConversionManager { // ConversionStorage instance which is scoped to lifetime of // |storage_task_runner_|. |storage_| should be accessed by calling // base::PostTask with |storage_task_runner_|, and should not be accessed - // directly. |storage_| can be null if the storage initialization did not - // succeed. + // directly. |storage_| should never be nullptr. // // TODO(https://crbug.com/1066920): This should use base::SequenceBound to // avoid having to call PostTask manually, as well as use base::Unretained on diff --git a/chromium/content/browser/conversions/conversion_manager_impl_unittest.cc b/chromium/content/browser/conversions/conversion_manager_impl_unittest.cc index 3ca019fd0d1..03e6245f9f8 100644 --- a/chromium/content/browser/conversions/conversion_manager_impl_unittest.cc +++ b/chromium/content/browser/conversions/conversion_manager_impl_unittest.cc @@ -18,6 +18,7 @@ #include "base/test/bind_test_util.h" #include "base/time/clock.h" #include "base/time/time.h" +#include "build/build_config.h" #include "content/browser/conversions/conversion_report.h" #include "content/browser/conversions/conversion_test_utils.h" #include "content/browser/conversions/storable_conversion.h" @@ -313,7 +314,14 @@ TEST_F(ConversionManagerImplTest, ConversionsSentFromUI_ReportedImmediately) { EXPECT_EQ(2u, test_reporter_->num_reports()); } -TEST_F(ConversionManagerImplTest, ExpiredReportsAtStartup_Delayed) { +// TODO(crbug.com/1088449): Flaky on Linux and Android. +#if defined(OS_LINUX) || defined(OS_ANDROID) +#define MAYBE_ExpiredReportsAtStartup_Delayed \ + DISABLED_ExpiredReportsAtStartup_Delayed +#else +#define MAYBE_ExpiredReportsAtStartup_Delayed ExpiredReportsAtStartup_Delayed +#endif +TEST_F(ConversionManagerImplTest, MAYBE_ExpiredReportsAtStartup_Delayed) { // Create a report that will be reported at t= 2 days. base::Time start_time = clock().Now(); conversion_manager_->HandleImpression( diff --git a/chromium/content/browser/conversions/conversion_registration_browsertest.cc b/chromium/content/browser/conversions/conversion_registration_browsertest.cc index 8b6fce9261d..b0251de1802 100644 --- a/chromium/content/browser/conversions/conversion_registration_browsertest.cc +++ b/chromium/content/browser/conversions/conversion_registration_browsertest.cc @@ -8,8 +8,10 @@ #include "base/bind.h" #include "base/test/scoped_feature_list.h" #include "content/browser/conversions/conversion_host.h" +#include "content/browser/conversions/conversion_manager_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" @@ -75,9 +77,10 @@ class TestConversionHost : public ConversionHost { base::RunLoop conversion_waiter_; }; -class ConversionRegistrationBrowserTest : public ContentBrowserTest { +class ConversionDisabledBrowserTest : public ContentBrowserTest { public: - ConversionRegistrationBrowserTest() { + ConversionDisabledBrowserTest() { + ConversionManagerImpl::RunInMemoryForTesting(); feature_list_.InitAndEnableFeature(features::kConversionMeasurement); } @@ -102,15 +105,41 @@ class ConversionRegistrationBrowserTest : public ContentBrowserTest { net::EmbeddedTestServer* https_server() { return https_server_.get(); } - private: + protected: base::test::ScopedFeatureList feature_list_; + + private: std::unique_ptr<net::EmbeddedTestServer> https_server_; }; +IN_PROC_BROWSER_TEST_F( + ConversionDisabledBrowserTest, + ConversionRegisteredWithoutOTEnabled_NoConversionDataReceived) { + EXPECT_TRUE(NavigateToURL( + shell(), + embedded_test_server()->GetURL("/page_with_conversion_redirect.html"))); + std::unique_ptr<TestConversionHost> host = + TestConversionHost::ReplaceAndGetConversionHost(web_contents()); + + EXPECT_TRUE(ExecJs(web_contents(), "registerConversion(123)")); + + EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); + EXPECT_EQ(0u, host->num_conversions()); +} + +class ConversionRegistrationBrowserTest : public ConversionDisabledBrowserTest { + public: + ConversionRegistrationBrowserTest() = default; + + void SetUpCommandLine(base::CommandLine* command_line) override { + // Sets up the blink runtime feature for ConversionMeasurement. + command_line->AppendSwitch( + switches::kEnableExperimentalWebPlatformFeatures); + } +}; + // Test that full conversion path does not cause any failure when a conversion // registration mojo is received. -// TODO(johnidel): This should really be testing internals of ConversionHost. -// That is trivial when reporting for conversions is added. IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest, ConversionRegistration_NoCrash) { EXPECT_TRUE(NavigateToURL( @@ -180,6 +209,60 @@ IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest, EXPECT_EQ(0u, host->num_conversions()); } +IN_PROC_BROWSER_TEST_F( + ConversionRegistrationBrowserTest, + ConversionRegistrationNotSameOriginRedirect_NotReceived) { + EXPECT_TRUE(NavigateToURL( + shell(), + https_server()->GetURL("c.test", "/page_with_conversion_redirect.html"))); + std::unique_ptr<TestConversionHost> host = + TestConversionHost::ReplaceAndGetConversionHost(web_contents()); + + // Create a url that does the following redirect chain b.test -> + // a.test/.well-known/...; this conversion registration should not be allowed, + // a.test did not initiate the redirect to the reporting endpoint. + GURL redirect_url = https_server()->GetURL( + "a.test", "/" + kWellKnownUrl + "?conversion-data=200"); + GURL registration_url = https_server()->GetURL( + "b.test", "/server-redirect?" + redirect_url.spec()); + + // Create a load observer that will wait for the redirect to complete. If a + // conversion was registered, this redirect would never complete. + ResourceLoadObserver load_observer(shell()); + EXPECT_TRUE(ExecJs(web_contents(), + JsReplace("createTrackingPixel($1);", registration_url))); + load_observer.WaitForResourceCompletion(registration_url); + + // Conversion mojo messages are sent on the same message pipe as navigation + // messages. Because the conversion would have been sequenced prior to the + // navigation message, it would be observed before the NavigateToURL() call + // finishes. + EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank"))); + EXPECT_EQ(0u, host->num_conversions()); +} + +IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest, + ConversionRegistrationIsSameOriginRedirect_Received) { + EXPECT_TRUE(NavigateToURL( + shell(), + https_server()->GetURL("c.test", "/page_with_conversion_redirect.html"))); + std::unique_ptr<TestConversionHost> host = + TestConversionHost::ReplaceAndGetConversionHost(web_contents()); + + // Create a url that does the following redirect chain b.test -> a.test -> + // a.test/.well-known/...; this conversion registration should be allowed. + GURL well_known_url = https_server()->GetURL( + "a.test", "/" + kWellKnownUrl + "?conversion-data=200"); + GURL redirect_url = https_server()->GetURL( + "a.test", "/server-redirect?" + well_known_url.spec()); + GURL registration_url = https_server()->GetURL( + "b.test", "/server-redirect?" + redirect_url.spec()); + + EXPECT_TRUE(ExecJs(web_contents(), + JsReplace("createTrackingPixel($1);", registration_url))); + EXPECT_EQ(200UL, host->WaitForNumConversions(1)); +} + IN_PROC_BROWSER_TEST_F(ConversionRegistrationBrowserTest, ConversionRegistrationInPreload_NotReceived) { std::unique_ptr<TestConversionHost> host = diff --git a/chromium/content/browser/conversions/conversion_reporter_impl.cc b/chromium/content/browser/conversions/conversion_reporter_impl.cc index 3011e2efc51..76797a44d71 100644 --- a/chromium/content/browser/conversions/conversion_reporter_impl.cc +++ b/chromium/content/browser/conversions/conversion_reporter_impl.cc @@ -10,6 +10,9 @@ #include "base/time/clock.h" #include "content/browser/conversions/conversion_manager.h" #include "content/browser/conversions/conversion_network_sender_impl.h" +#include "content/browser/storage_partition_impl.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/common/content_client.h" namespace content { @@ -17,6 +20,7 @@ ConversionReporterImpl::ConversionReporterImpl( StoragePartition* storage_partition, const base::Clock* clock) : clock_(clock), + partition_(static_cast<StoragePartitionImpl*>(storage_partition)), network_sender_( std::make_unique<ConversionNetworkSenderImpl>(storage_partition)) {} @@ -60,11 +64,20 @@ void ConversionReporterImpl::SendNextReport() { // Send the next report and remove it from the queue. Bind the conversion id // to the sent callback so we know which conversion report has finished // sending. - network_sender_->SendReport( - report_queue_.top().get(), - base::BindOnce(&ConversionReporterImpl::OnReportSent, - base::Unretained(this), - *report_queue_.top()->conversion_id)); + if (GetContentClient()->browser()->AllowConversionMeasurement( + partition_->browser_context())) { + network_sender_->SendReport( + report_queue_.top().get(), + base::BindOnce(&ConversionReporterImpl::OnReportSent, + base::Unretained(this), + *report_queue_.top()->conversion_id)); + } else { + // If measurement is disallowed, just drop the report on the floor. We need + // to make sure we forward that the report was "sent" to ensure it is + // deleted from storage, etc. This simulate sending the report through a + // null channel. + OnReportSent(*report_queue_.top()->conversion_id); + } report_queue_.pop(); MaybeScheduleNextReport(); } diff --git a/chromium/content/browser/conversions/conversion_reporter_impl.h b/chromium/content/browser/conversions/conversion_reporter_impl.h index 855a2b3a4f4..d1ba6c77acb 100644 --- a/chromium/content/browser/conversions/conversion_reporter_impl.h +++ b/chromium/content/browser/conversions/conversion_reporter_impl.h @@ -26,6 +26,7 @@ class Clock; namespace content { class StoragePartition; +class StoragePartitionImpl; // This class is responsible for managing the dispatch of conversion reports to // a ConversionReporterImpl::NetworkSender. It maintains a queue of reports and @@ -96,6 +97,10 @@ class CONTENT_EXPORT ConversionReporterImpl const base::Clock* clock_; + // Should never be nullptr, since StoragePartition owns the ConversionManager + // which owns |this|. + StoragePartitionImpl* partition_; + // Timer which signals the next report in |report_queue_| should be sent. base::OneShotTimer send_report_timer_; diff --git a/chromium/content/browser/conversions/conversion_reporter_impl_unittest.cc b/chromium/content/browser/conversions/conversion_reporter_impl_unittest.cc index a2b030ed8f6..d9e40dece4d 100644 --- a/chromium/content/browser/conversions/conversion_reporter_impl_unittest.cc +++ b/chromium/content/browser/conversions/conversion_reporter_impl_unittest.cc @@ -16,6 +16,7 @@ #include "content/browser/conversions/conversion_test_utils.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/storage_partition.h" +#include "content/public/common/content_client.h" #include "content/public/test/browser_task_environment.h" #include "content/public/test/test_browser_context.h" #include "testing/gtest/include/gtest/gtest.h" @@ -200,4 +201,20 @@ TEST_F(ConversionReporterImplTest, ManyReportsAddedSeparately_SentInOrder) { } } +TEST_F(ConversionReporterImplTest, EmbedderDisallowsConversions_ReportNotSent) { + ConversionDisallowingContentBrowserClient disallowed_browser_client; + ContentBrowserClient* old_browser_client = + SetBrowserClientForTesting(&disallowed_browser_client); + reporter_->AddReportsToQueue({GetReport(clock().Now(), /*conversion_id=*/1)}, + base::BindRepeating([](int64_t conversion_id) { + EXPECT_EQ(1L, conversion_id); + })); + + // Fast forward by 0, as we yield the thread when a report is scheduled to be + // sent. + task_environment_.FastForwardBy(base::TimeDelta()); + EXPECT_EQ(0u, sender_->num_reports_sent()); + SetBrowserClientForTesting(old_browser_client); +} + } // namespace content diff --git a/chromium/content/browser/conversions/conversion_storage.h b/chromium/content/browser/conversions/conversion_storage.h index 1c8feed1ccf..3d999f3179d 100644 --- a/chromium/content/browser/conversions/conversion_storage.h +++ b/chromium/content/browser/conversions/conversion_storage.h @@ -61,6 +61,9 @@ class ConversionStorage { }; virtual ~ConversionStorage() = default; + // When adding a new method, also add it to + // ConversionStorageTest.StorageUsedAfterFailedInitilization_FailsSilently. + // Initializes the storage. Returns true on success, otherwise the storage // should not be used. virtual bool Initialize() = 0; diff --git a/chromium/content/browser/conversions/conversion_storage_sql.cc b/chromium/content/browser/conversions/conversion_storage_sql.cc index a921f883991..80604e1ca3d 100644 --- a/chromium/content/browser/conversions/conversion_storage_sql.cc +++ b/chromium/content/browser/conversions/conversion_storage_sql.cc @@ -26,9 +26,6 @@ namespace content { namespace { -const base::FilePath::CharType kDatabaseName[] = - FILE_PATH_LITERAL("Conversions"); - std::string SerializeOrigin(const url::Origin& origin) { // Conversion API is only designed to be used for secure // contexts (targets and reporting endpoints). We should have filtered out bad @@ -50,13 +47,28 @@ base::Time DeserializeTime(int64_t microseconds) { base::TimeDelta::FromMicroseconds(microseconds)); } +const base::FilePath::CharType kInMemoryPath[] = FILE_PATH_LITERAL(":memory"); + +const base::FilePath::CharType kDatabasePath[] = + FILE_PATH_LITERAL("Conversions"); + } // namespace +// static +void ConversionStorageSql::RunInMemoryForTesting() { + g_run_in_memory_ = true; +} + +// static +bool ConversionStorageSql::g_run_in_memory_ = false; + ConversionStorageSql::ConversionStorageSql( - const base::FilePath& path_to_database_dir, + const base::FilePath& path_to_database, std::unique_ptr<Delegate> delegate, const base::Clock* clock) - : path_to_database_(path_to_database_dir.Append(kDatabaseName)), + : path_to_database_(g_run_in_memory_ + ? base::FilePath(kInMemoryPath) + : path_to_database.Append(kDatabasePath)), clock_(clock), delegate_(std::move(delegate)), weak_factory_(this) { @@ -71,22 +83,37 @@ ConversionStorageSql::~ConversionStorageSql() { bool ConversionStorageSql::Initialize() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - db_.set_histogram_tag("Conversions"); + db_ = std::make_unique<sql::Database>(); + db_->set_histogram_tag("Conversions"); // Supply this callback with a weak_ptr to avoid calling the error callback // after |this| has been deleted. - db_.set_error_callback( + db_->set_error_callback( base::BindRepeating(&ConversionStorageSql::DatabaseErrorCallback, weak_factory_.GetWeakPtr())); - db_.set_page_size(4096); - db_.set_cache_size(32); - db_.set_exclusive_locking(); - return db_.Open(path_to_database_) && InitializeSchema(); + db_->set_page_size(4096); + db_->set_cache_size(32); + db_->set_exclusive_locking(); + + bool opened = (path_to_database_.value() == kInMemoryPath) + ? db_->OpenInMemory() + : db_->Open(path_to_database_); + + if (!opened || !InitializeSchema()) { + db_.reset(); + db_is_open_ = false; + return false; + } + + db_is_open_ = true; + return true; } void ConversionStorageSql::StoreImpression( const StorableImpression& impression) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return; // Cleanup any impression that may be expired by this point. This is done when // an impression is added to prevent additional logic for cleaning the table @@ -104,7 +131,7 @@ void ConversionStorageSql::StoreImpression( // Wrap the deactivation and insertion in the same transaction. If the // deactivation fails, we do not want to store the new impression as we may // return the wrong set of impressions for a conversion. - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return; @@ -120,7 +147,7 @@ void ConversionStorageSql::StoreImpression( "UPDATE impressions SET active = 0 " "WHERE conversion_origin = ? AND reporting_origin = ? AND " "active = 1 AND num_conversions > 0"; - sql::Statement deactivate_statement(db_.GetCachedStatement( + sql::Statement deactivate_statement(db_->GetCachedStatement( SQL_FROM_HERE, kDeactivateMatchingConvertedImpressionsSql)); deactivate_statement.BindString(0, serialized_conversion_origin); deactivate_statement.BindString(1, serialized_reporting_origin); @@ -132,7 +159,7 @@ void ConversionStorageSql::StoreImpression( "reporting_origin, impression_time, expiry_time) " "VALUES (?,?,?,?,?,?)"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kInsertImpressionSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kInsertImpressionSql)); statement.BindString(0, impression.impression_data()); statement.BindString(1, serialized_impression_origin); statement.BindString(2, serialized_conversion_origin); @@ -147,6 +174,8 @@ void ConversionStorageSql::StoreImpression( int ConversionStorageSql::MaybeCreateAndStoreConversionReports( const StorableConversion& conversion) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return 0; const url::Origin& conversion_origin = conversion.conversion_origin(); const std::string serialized_conversion_origin = @@ -171,7 +200,7 @@ int ConversionStorageSql::MaybeCreateAndStoreConversionReports( "ORDER BY impression_time DESC"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kGetMatchingImpressionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kGetMatchingImpressionsSql)); statement.BindString(0, serialized_conversion_origin); statement.BindString(1, SerializeOrigin(reporting_origin)); statement.BindInt64(2, serialized_current_time); @@ -212,7 +241,7 @@ int ConversionStorageSql::MaybeCreateAndStoreConversionReports( if (new_reports.empty()) return 0; - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return 0; @@ -221,7 +250,7 @@ int ConversionStorageSql::MaybeCreateAndStoreConversionReports( "(impression_id, conversion_data, conversion_time, report_time, " "attribution_credit) VALUES(?,?,?,?,?)"; sql::Statement store_conversion_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kStoreConversionSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kStoreConversionSql)); // Mark impressions inactive if they hit the max conversions allowed limit // supplied by the delegate. Because only active impressions log conversions, @@ -233,8 +262,8 @@ int ConversionStorageSql::MaybeCreateAndStoreConversionReports( "UPDATE impressions SET num_conversions = num_conversions + 1, " "active = num_conversions < ? " "WHERE impression_id = ?"; - sql::Statement impression_update_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kUpdateImpressionForConversionSql)); + sql::Statement impression_update_statement(db_->GetCachedStatement( + SQL_FROM_HERE, kUpdateImpressionForConversionSql)); // Subtract one from the max number of conversions per the query comment // above. We need to account for the new conversion in this comparison so we @@ -270,6 +299,8 @@ int ConversionStorageSql::MaybeCreateAndStoreConversionReports( std::vector<ConversionReport> ConversionStorageSql::GetConversionsToReport( base::Time max_report_time) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return {}; // Get all entries in the conversions table with a |report_time| less than // |expired_at| and their matching information from the impression table. @@ -281,7 +312,7 @@ std::vector<ConversionReport> ConversionStorageSql::GetConversionsToReport( "FROM conversions C JOIN impressions I ON " "C.impression_id = I.impression_id WHERE C.report_time <= ?"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kGetExpiredConversionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kGetExpiredConversionsSql)); statement.BindInt64(0, SerializeTime(max_report_time)); std::vector<ConversionReport> conversions; @@ -328,12 +359,16 @@ std::vector<ConversionReport> ConversionStorageSql::GetConversionsToReport( } std::vector<StorableImpression> ConversionStorageSql::GetActiveImpressions() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return {}; + const char kGetImpressionsSql[] = "SELECT impression_data, impression_origin, conversion_origin, " "reporting_origin, impression_time, expiry_time, impression_id " "FROM impressions WHERE active = 1 AND expiry_time > ?"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kGetImpressionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kGetImpressionsSql)); statement.BindInt64(0, SerializeTime(clock_->Now())); std::vector<StorableImpression> impressions; @@ -359,17 +394,21 @@ std::vector<StorableImpression> ConversionStorageSql::GetActiveImpressions() { } int ConversionStorageSql::DeleteExpiredImpressions() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return 0; + // Delete all impressions that have no associated conversions and are past // their expiry time. Optimized by |kImpressionExpiryIndexSql|. const char kDeleteExpiredImpressionsSql[] = "DELETE FROM impressions WHERE expiry_time <= ? AND " "impression_id NOT IN (SELECT impression_id FROM conversions)"; sql::Statement delete_expired_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteExpiredImpressionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteExpiredImpressionsSql)); delete_expired_statement.BindInt64(0, SerializeTime(clock_->Now())); if (!delete_expired_statement.Run()) return 0; - int change_count = db_.GetLastChangeCount(); + int change_count = db_->GetLastChangeCount(); // Delete all impressions that have no associated conversions and are // inactive. This is done in a separate statement from @@ -379,31 +418,39 @@ int ConversionStorageSql::DeleteExpiredImpressions() { "DELETE FROM impressions WHERE active = 0 AND " "impression_id NOT IN (SELECT impression_id FROM conversions)"; sql::Statement delete_inactive_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteInactiveImpressionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteInactiveImpressionsSql)); if (!delete_inactive_statement.Run()) return change_count; - return change_count + db_.GetLastChangeCount(); + return change_count + db_->GetLastChangeCount(); } bool ConversionStorageSql::DeleteConversion(int64_t conversion_id) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return false; + // Delete the row identified by |conversion_id|. const char kDeleteSentConversionSql[] = "DELETE FROM conversions WHERE conversion_id = ?"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteSentConversionSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteSentConversionSql)); statement.BindInt64(0, conversion_id); if (!statement.Run()) return false; - return db_.GetLastChangeCount() > 0; + return db_->GetLastChangeCount() > 0; } void ConversionStorageSql::ClearData( base::Time delete_begin, base::Time delete_end, base::RepeatingCallback<bool(const url::Origin&)> filter) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!db_is_open_) + return; + SCOPED_UMA_HISTOGRAM_TIMER("Conversions.ClearDataTime"); if (filter.is_null()) { ClearAllDataInRange(delete_begin, delete_end); @@ -422,7 +469,7 @@ void ConversionStorageSql::ClearData( "(I.impression_time BETWEEN ?1 AND ?2) OR" "(C.conversion_time BETWEEN ?1 AND ?2)"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kScanCandidateData)); + db_->GetCachedStatement(SQL_FROM_HERE, kScanCandidateData)); statement.BindInt64(0, SerializeTime(delete_begin)); statement.BindInt64(1, SerializeTime(delete_end)); @@ -455,7 +502,7 @@ void ConversionStorageSql::ClearData( // Delete the data in a transaction to avoid cases where the impression part // of a conversion is deleted without deleting the associated conversion, or // vice versa. - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return; @@ -463,7 +510,7 @@ void ConversionStorageSql::ClearData( const char kDeleteImpressionSql[] = "DELETE FROM impressions WHERE impression_id = ?"; sql::Statement impression_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteImpressionSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteImpressionSql)); impression_statement.BindInt64(0, impression_id); if (!impression_statement.Run()) return; @@ -473,7 +520,7 @@ void ConversionStorageSql::ClearData( const char kDeleteConversionSql[] = "DELETE FROM conversions WHERE conversion_id = ?"; sql::Statement conversion_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteConversionSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteConversionSql)); conversion_statement.BindInt64(0, conversion_id); if (!conversion_statement.Run()) return; @@ -489,7 +536,7 @@ void ConversionStorageSql::ClearData( const char kDeleteVestigialConversionSql[] = "DELETE FROM conversions WHERE impression_id = ?"; sql::Statement delete_vestigial_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteVestigialConversionSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteVestigialConversionSql)); delete_vestigial_statement.BindInt64(0, impression_id); if (!delete_vestigial_statement.Run()) return; @@ -508,7 +555,7 @@ void ConversionStorageSql::ClearAllDataInRange(base::Time delete_begin, return; } - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return; @@ -524,7 +571,7 @@ void ConversionStorageSql::ClearAllDataInRange(base::Time delete_begin, "impression_id in (SELECT impression_id FROM conversions " "WHERE conversion_time BETWEEN ?1 AND ?2)"; sql::Statement delete_impressions_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteImpressionRangeSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteImpressionRangeSql)); delete_impressions_statement.BindInt64(0, SerializeTime(delete_begin)); delete_impressions_statement.BindInt64(1, SerializeTime(delete_end)); if (!delete_impressions_statement.Run()) @@ -534,7 +581,7 @@ void ConversionStorageSql::ClearAllDataInRange(base::Time delete_begin, "DELETE FROM conversions WHERE (conversion_time BETWEEN ? AND ?) " "OR impression_id NOT IN (SELECT impression_id FROM impressions)"; sql::Statement delete_conversions_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteConversionRangeSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteConversionRangeSql)); delete_conversions_statement.BindInt64(0, SerializeTime(delete_begin)); delete_conversions_statement.BindInt64(1, SerializeTime(delete_end)); if (!delete_conversions_statement.Run()) @@ -543,15 +590,15 @@ void ConversionStorageSql::ClearAllDataInRange(base::Time delete_begin, } void ConversionStorageSql::ClearAllDataAllTime() { - sql::Transaction transaction(&db_); + sql::Transaction transaction(db_.get()); if (!transaction.Begin()) return; const char kDeleteAllConversionsSql[] = "DELETE FROM conversions"; const char kDeleteAllImpressionsSql[] = "DELETE FROM impressions"; sql::Statement delete_all_conversions_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteAllConversionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllConversionsSql)); sql::Statement delete_all_impressions_statement( - db_.GetCachedStatement(SQL_FROM_HERE, kDeleteAllImpressionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllImpressionsSql)); if (!delete_all_conversions_statement.Run()) return; if (!delete_all_impressions_statement.Run()) @@ -566,7 +613,7 @@ bool ConversionStorageSql::HasCapacityForStoringImpression( "SELECT COUNT(impression_origin) FROM impressions WHERE " "impression_origin = ?"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kCountImpressionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kCountImpressionsSql)); statement.BindString(0, serialized_origin); if (!statement.Step()) return false; @@ -588,7 +635,7 @@ bool ConversionStorageSql::HasCapacityForStoringConversion( " I.impression_id = C.impression_id" " WHERE I.conversion_origin = ? AND (active BETWEEN 0 AND 1)"; sql::Statement statement( - db_.GetCachedStatement(SQL_FROM_HERE, kCountConversionsSql)); + db_->GetCachedStatement(SQL_FROM_HERE, kCountConversionsSql)); statement.BindString(0, serialized_origin); if (!statement.Step()) return false; @@ -629,7 +676,7 @@ bool ConversionStorageSql::InitializeSchema() { " expiry_time INTEGER NOT NULL," " num_conversions INTEGER DEFAULT 0," " active INTEGER DEFAULT 1)"; - if (!db_.Execute(kImpressionTableSql)) + if (!db_->Execute(kImpressionTableSql)) return false; // Optimizes impression lookup by conversion/reporting origin during calls to @@ -640,7 +687,7 @@ bool ConversionStorageSql::InitializeSchema() { const char kConversionUrlIndexSql[] = "CREATE INDEX IF NOT EXISTS conversion_origin_idx " "ON impressions(active, conversion_origin, reporting_origin)"; - if (!db_.Execute(kConversionUrlIndexSql)) + if (!db_->Execute(kConversionUrlIndexSql)) return false; // Optimizes calls to DeleteExpiredImpressions() and @@ -650,14 +697,14 @@ bool ConversionStorageSql::InitializeSchema() { const char kImpressionExpiryIndexSql[] = "CREATE INDEX IF NOT EXISTS impression_expiry_idx " "ON impressions(expiry_time)"; - if (!db_.Execute(kImpressionExpiryIndexSql)) + if (!db_->Execute(kImpressionExpiryIndexSql)) return false; // Optimizes counting impressions by impression origin. const char kImpressionOriginIndexSql[] = "CREATE INDEX IF NOT EXISTS impression_origin_idx " "ON impressions(impression_origin)"; - if (!db_.Execute(kImpressionOriginIndexSql)) + if (!db_->Execute(kImpressionOriginIndexSql)) return false; // All columns in this table are const. |impression_id| is the primary key of @@ -675,7 +722,7 @@ bool ConversionStorageSql::InitializeSchema() { " conversion_time INTEGER NOT NULL," " report_time INTEGER NOT NULL," " attribution_credit INTEGER NOT NULL)"; - if (!db_.Execute(kConversionTableSql)) + if (!db_->Execute(kConversionTableSql)) return false; // Optimize sorting conversions by report time for calls to @@ -684,7 +731,7 @@ bool ConversionStorageSql::InitializeSchema() { const char kConversionReportTimeIndexSql[] = "CREATE INDEX IF NOT EXISTS conversion_report_idx " "ON conversions(report_time)"; - if (!db_.Execute(kConversionReportTimeIndexSql)) + if (!db_->Execute(kConversionReportTimeIndexSql)) return false; // Want to optimize conversion look up by click id. This allows us to @@ -694,20 +741,21 @@ bool ConversionStorageSql::InitializeSchema() { const char kConversionClickIdIndexSql[] = "CREATE INDEX IF NOT EXISTS conversion_impression_id_idx " "ON conversions(impression_id)"; - return db_.Execute(kConversionClickIdIndexSql); + return db_->Execute(kConversionClickIdIndexSql); } void ConversionStorageSql::DatabaseErrorCallback(int extended_error, sql::Statement* stmt) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Attempt to recover corrupt databases. - if (sql::Recovery::ShouldRecover(extended_error)) { + // Attempt to recover a corrupt database, unless it is setup in memory. + if (sql::Recovery::ShouldRecover(extended_error) && + (path_to_database_.value() != kInMemoryPath)) { // Prevent reentrant calls. - db_.reset_error_callback(); + db_->reset_error_callback(); // After this call, the |db_| handle is poisoned so that future calls will // return errors until the handle is re-opened. - sql::Recovery::RecoverDatabase(&db_, path_to_database_); + sql::Recovery::RecoverDatabase(db_.get(), path_to_database_); // The DLOG(FATAL) below is intended to draw immediate attention to errors // in newly-written code. Database corruption is generally a result of OS @@ -719,8 +767,13 @@ void ConversionStorageSql::DatabaseErrorCallback(int extended_error, } // The default handling is to assert on debug and to ignore on release. - if (!sql::Database::IsExpectedSqliteError(extended_error)) - DLOG(FATAL) << db_.GetErrorMessage(); + if (!sql::Database::IsExpectedSqliteError(extended_error) && + !ignore_errors_for_testing_) + DLOG(FATAL) << db_->GetErrorMessage(); + + // Consider the database closed if we did not attempt to recover so we did + // not produce further errors. + db_is_open_ = false; } } // namespace content diff --git a/chromium/content/browser/conversions/conversion_storage_sql.h b/chromium/content/browser/conversions/conversion_storage_sql.h index 686df2f1658..69144beb5b9 100644 --- a/chromium/content/browser/conversions/conversion_storage_sql.h +++ b/chromium/content/browser/conversions/conversion_storage_sql.h @@ -28,13 +28,19 @@ namespace content { // destroyed on the same sequence. The sequence must outlive |this|. class CONTENT_EXPORT ConversionStorageSql : public ConversionStorage { public: - ConversionStorageSql(const base::FilePath& path_to_database_dir, + static void RunInMemoryForTesting(); + + ConversionStorageSql(const base::FilePath& path_to_database, std::unique_ptr<Delegate> delegate, const base::Clock* clock); ConversionStorageSql(const ConversionStorageSql& other) = delete; ConversionStorageSql& operator=(const ConversionStorageSql& other) = delete; ~ConversionStorageSql() override; + void set_ignore_errors_for_testing(bool ignore_for_testing) { + ignore_errors_for_testing_ = ignore_for_testing; + } + private: // ConversionStorage bool Initialize() override; @@ -62,8 +68,21 @@ class CONTENT_EXPORT ConversionStorageSql : public ConversionStorage { void DatabaseErrorCallback(int extended_error, sql::Statement* stmt); + static bool g_run_in_memory_; + + // If set, database errors will not crash the client when run in debug mode. + bool ignore_errors_for_testing_ = false; + const base::FilePath path_to_database_; - sql::Database db_; + + // Whether the db is open and should be accessed. False if database + // initialization failed, or if the db suffered from an unrecoverable error. + bool db_is_open_ = false; + + // May be null if the database: + // - could not be opened + // - table/index initialization failed + std::unique_ptr<sql::Database> db_; // Must outlive |this|. const base::Clock* clock_; diff --git a/chromium/content/browser/conversions/conversion_storage_sql_unittest.cc b/chromium/content/browser/conversions/conversion_storage_sql_unittest.cc index ca408b729c3..5b501d80bd9 100644 --- a/chromium/content/browser/conversions/conversion_storage_sql_unittest.cc +++ b/chromium/content/browser/conversions/conversion_storage_sql_unittest.cc @@ -8,6 +8,7 @@ #include <memory> #include "base/bind.h" +#include "base/files/file_util.h" #include "base/files/scoped_temp_dir.h" #include "base/run_loop.h" #include "base/test/simple_test_clock.h" @@ -55,8 +56,10 @@ class ConversionStorageSqlTest : public testing::Test { ConfigurableStorageDelegate* delegate() { return delegate_; } - private: + protected: base::ScopedTempDir temp_directory_; + + private: std::unique_ptr<ConversionStorage> storage_; ConfigurableStorageDelegate* delegate_ = nullptr; base::SimpleTestClock clock_; @@ -259,4 +262,18 @@ TEST_F(ConversionStorageSqlTest, MaxConversionsPerOrigin) { EXPECT_EQ(2u, conversion_rows); } +TEST_F(ConversionStorageSqlTest, CantOpenDb_FailsSilentlyInRelease) { + base::CreateDirectoryAndGetError(db_path(), nullptr); + + auto sql_storage = std::make_unique<ConversionStorageSql>( + temp_directory_.GetPath(), + std::make_unique<ConfigurableStorageDelegate>(), clock()); + sql_storage->set_ignore_errors_for_testing(true); + + // Initialize() is private on ConserionStorageSql, so convert to + // ConversionStorage. + std::unique_ptr<ConversionStorage> storage = std::move(sql_storage); + EXPECT_FALSE(storage->Initialize()); +} + } // namespace content diff --git a/chromium/content/browser/conversions/conversion_storage_unittest.cc b/chromium/content/browser/conversions/conversion_storage_unittest.cc index b8089b96a06..7b17142cfe0 100644 --- a/chromium/content/browser/conversions/conversion_storage_unittest.cc +++ b/chromium/content/browser/conversions/conversion_storage_unittest.cc @@ -88,13 +88,36 @@ class ConversionStorageTest : public testing::Test { ConfigurableStorageDelegate* delegate() { return delegate_; } + protected: + base::ScopedTempDir dir_; + private: ConfigurableStorageDelegate* delegate_; base::SimpleTestClock clock_; - base::ScopedTempDir dir_; std::unique_ptr<ConversionStorage> storage_; }; +TEST_F(ConversionStorageTest, + StorageUsedAfterFailedInitilization_FailsSilently) { + // We fake a failed initialization by never initializing |storage|. + std::unique_ptr<ConversionStorage> storage = + std::make_unique<ConversionStorageSql>( + dir_.GetPath(), std::make_unique<ConfigurableStorageDelegate>(), + clock()); + + // Test all public methods on ConversionStorage. + EXPECT_NO_FATAL_FAILURE( + storage->StoreImpression(ImpressionBuilder(clock()->Now()).Build())); + EXPECT_EQ(0, + storage->MaybeCreateAndStoreConversionReports(DefaultConversion())); + EXPECT_TRUE(storage->GetConversionsToReport(clock()->Now()).empty()); + EXPECT_TRUE(storage->GetActiveImpressions().empty()); + EXPECT_EQ(0, storage->DeleteExpiredImpressions()); + EXPECT_EQ(0, storage->DeleteConversion(0UL)); + EXPECT_NO_FATAL_FAILURE(storage->ClearData( + base::Time::Min(), base::Time::Max(), base::NullCallback())); +} + TEST_F(ConversionStorageTest, ImpressionStoredAndRetrieved_ValuesIdentical) { auto impression = ImpressionBuilder(clock()->Now()).Build(); storage()->StoreImpression(impression); diff --git a/chromium/content/browser/conversions/conversion_test_utils.cc b/chromium/content/browser/conversions/conversion_test_utils.cc index 10f95f365bf..3dc13c9b8aa 100644 --- a/chromium/content/browser/conversions/conversion_test_utils.cc +++ b/chromium/content/browser/conversions/conversion_test_utils.cc @@ -29,6 +29,11 @@ const int64_t kExpiryTime = 30; } // namespace +bool ConversionDisallowingContentBrowserClient::AllowConversionMeasurement( + BrowserContext* context) { + return false; +} + ConfigurableStorageDelegate::ConfigurableStorageDelegate() = default; ConfigurableStorageDelegate::~ConfigurableStorageDelegate() = default; diff --git a/chromium/content/browser/conversions/conversion_test_utils.h b/chromium/content/browser/conversions/conversion_test_utils.h index 80e569b5cfb..bae9cea4329 100644 --- a/chromium/content/browser/conversions/conversion_test_utils.h +++ b/chromium/content/browser/conversions/conversion_test_utils.h @@ -18,11 +18,22 @@ #include "content/browser/conversions/conversion_storage.h" #include "content/browser/conversions/storable_conversion.h" #include "content/browser/conversions/storable_impression.h" +#include "content/test/test_content_browser_client.h" #include "testing/gmock/include/gmock/gmock.h" #include "url/origin.h" namespace content { +class ConversionDisallowingContentBrowserClient + : public TestContentBrowserClient { + public: + ConversionDisallowingContentBrowserClient() = default; + ~ConversionDisallowingContentBrowserClient() override = default; + + // ContentBrowserClient: + bool AllowConversionMeasurement(BrowserContext* context) override; +}; + class ConfigurableStorageDelegate : public ConversionStorage::Delegate { public: using AttributionCredits = std::list<int>; diff --git a/chromium/content/browser/conversions/conversions_browsertest.cc b/chromium/content/browser/conversions/conversions_browsertest.cc index dedcf5a42ec..6df8d3c536f 100644 --- a/chromium/content/browser/conversions/conversions_browsertest.cc +++ b/chromium/content/browser/conversions/conversions_browsertest.cc @@ -5,7 +5,12 @@ #include <memory> #include "base/command_line.h" +#include "base/sequenced_task_runner.h" #include "base/test/scoped_feature_list.h" +#include "base/threading/sequenced_task_runner_handle.h" +#include "content/browser/conversions/conversion_manager_impl.h" +#include "content/browser/conversions/conversion_test_utils.h" +#include "content/public/common/content_client.h" #include "content/public/common/content_features.h" #include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" @@ -14,6 +19,7 @@ #include "content/public/test/content_browser_test_utils.h" #include "content/public/test/test_navigation_observer.h" #include "content/shell/browser/shell.h" +#include "content/test/test_content_browser_client.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/controllable_http_response.h" #include "net/test/embedded_test_server/default_handlers.h" @@ -45,6 +51,8 @@ struct ExpectedReportWaiter { GURL expected_url; std::unique_ptr<net::test_server::ControllableHttpResponse> response; + bool HasRequest() { return !!response->http_request(); } + // Returns the url for the HttpRequest handled by |response|. This returns a // URL formatted with the host defined in the headers. This would not match // |expected_url| if the host for report url was not set properly. @@ -74,10 +82,15 @@ class ConversionsBrowserTest : public ContentBrowserTest { public: ConversionsBrowserTest() { feature_list_.InitAndEnableFeature(features::kConversionMeasurement); + ConversionManagerImpl::RunInMemoryForTesting(); } void SetUpCommandLine(base::CommandLine* command_line) override { command_line->AppendSwitch(switches::kConversionsDebugMode); + + // Sets up the blink runtime feature for ConversionMeasurement. + command_line->AppendSwitch( + switches::kEnableExperimentalWebPlatformFeatures); } void SetUpOnMainThread() override { @@ -95,11 +108,19 @@ class ConversionsBrowserTest : public ContentBrowserTest { net::EmbeddedTestServer* https_server() { return https_server_.get(); } + protected: + ConversionDisallowingContentBrowserClient disallowed_browser_client_; + private: base::test::ScopedFeatureList feature_list_; std::unique_ptr<net::EmbeddedTestServer> https_server_; }; +// Verifies that storage initialization does not hang when initialized in a +// browsertest context, see https://crbug.com/1080764). +IN_PROC_BROWSER_TEST_F(ConversionsBrowserTest, + FeatureEnabled_StorageInitWithoutHang) {} + IN_PROC_BROWSER_TEST_F(ConversionsBrowserTest, ImpressionConversion_ReportSent) { // Expected reports must be registered before the server starts. @@ -251,4 +272,57 @@ IN_PROC_BROWSER_TEST_F( } } +IN_PROC_BROWSER_TEST_F(ConversionsBrowserTest, + ConversionRegisteredWithEmbedderDisallow_NoData) { + ContentBrowserClient* old_browser_client = + SetBrowserClientForTesting(&disallowed_browser_client_); + + // Expected reports must be registered before the server starts. + ExpectedReportWaiter expected_report( + GURL( + "https://a.test/.well-known/" + "register-conversion?impression-data=1&conversion-data=7&credit=100"), + https_server()); + ASSERT_TRUE(https_server()->Start()); + + GURL impression_url = https_server()->GetURL( + "a.test", "/conversions/page_with_impression_creator.html"); + EXPECT_TRUE(NavigateToURL(web_contents(), impression_url)); + + // Create an anchor tag with impression attributes and click the link. By + // default the target is set to "_top". + GURL conversion_url = https_server()->GetURL( + "b.test", "/conversions/page_with_conversion_redirect.html"); + EXPECT_TRUE( + ExecJs(web_contents(), + JsReplace(R"( + createImpressionTag("link" /* id */, + $1 /* url */, + "1" /* impression data */, + $2 /* conversion_destination */);)", + conversion_url, url::Origin::Create(conversion_url)))); + + TestNavigationObserver observer(web_contents()); + EXPECT_TRUE(ExecJs(shell(), "simulateClick('link');")); + observer.Wait(); + + // Register a conversion with the original page as the reporting origin. + EXPECT_TRUE( + ExecJs(web_contents(), JsReplace("registerConversionForOrigin(7, $1)", + url::Origin::Create(impression_url)))); + + // Since we want to verify that a report _isn't_ sent, we can't really wait on + // any event here. The best thing we can do is just impose a short delay and + // verify the browser didn't send anything. Worst case, this should start + // flakily failing if the logic breaks. + base::RunLoop run_loop; + base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( + FROM_HERE, run_loop.QuitClosure(), + base::TimeDelta::FromMilliseconds(100)); + run_loop.Run(); + EXPECT_FALSE(expected_report.HasRequest()); + + SetBrowserClientForTesting(old_browser_client); +} + } // namespace content diff --git a/chromium/content/browser/conversions/conversions_origin_trial_browsertest.cc b/chromium/content/browser/conversions/conversions_origin_trial_browsertest.cc new file mode 100644 index 00000000000..d4dc3e137f5 --- /dev/null +++ b/chromium/content/browser/conversions/conversions_origin_trial_browsertest.cc @@ -0,0 +1,106 @@ +// Copyright 2019 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 <vector> + +#include "base/strings/strcat.h" +#include "base/test/bind_test_util.h" +#include "base/test/scoped_feature_list.h" +#include "content/browser/conversions/conversion_manager_impl.h" +#include "content/browser/conversions/storable_impression.h" +#include "content/browser/storage_partition_impl.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/navigation_handle.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/storage_partition.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_browser_test.h" +#include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" +#include "content/public/test/url_loader_interceptor.h" +#include "content/shell/browser/shell.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace content { + +namespace { +constexpr char kBaseDataDir[] = "content/test/data/conversions/"; +} + +class ConversionsOriginTrialBrowserTest : public ContentBrowserTest { + public: + ConversionsOriginTrialBrowserTest() { + feature_list_.InitAndEnableFeature(features::kConversionMeasurement); + } + + void SetUpOnMainThread() override { + ContentBrowserTest::SetUpOnMainThread(); + + // We use a URLLoaderInterceptor, rather than the EmbeddedTestServer, since + // the origin trial token in the response is associated with a fixed + // origin, whereas EmbeddedTestServer serves content on a random port. + url_loader_interceptor_ = + std::make_unique<URLLoaderInterceptor>(base::BindLambdaForTesting( + [&](URLLoaderInterceptor::RequestParams* params) -> bool { + URLLoaderInterceptor::WriteResponse( + base::StrCat( + {kBaseDataDir, params->url_request.url.path_piece()}), + params->client.get()); + return true; + })); + } + + void TearDownOnMainThread() override { url_loader_interceptor_.reset(); } + + WebContents* web_contents() { return shell()->web_contents(); } + + private: + base::test::ScopedFeatureList feature_list_; + std::unique_ptr<URLLoaderInterceptor> url_loader_interceptor_; +}; + +IN_PROC_BROWSER_TEST_F(ConversionsOriginTrialBrowserTest, + OriginTrialEnabled_ImpressionRegistered) { + EXPECT_TRUE(NavigateToURL( + shell(), GURL("https://example.test/impression_with_origin_trial.html"))); + + EXPECT_TRUE(ExecJs(shell(), R"( + createImpressionTag("link" /* id */, + "https://example.test/page_with_conversion_redirect.html" /* url */, + "1" /* impression data */, + "https://example.test/" /* conversion_destination */);)")); + + TestNavigationObserver observer(web_contents()); + EXPECT_TRUE(ExecJs(shell(), "simulateClick('link');")); + observer.Wait(); + + ConversionManagerImpl* conversion_manager = + static_cast<StoragePartitionImpl*>( + BrowserContext::GetDefaultStoragePartition( + web_contents()->GetBrowserContext())) + ->GetConversionManager(); + + base::RunLoop run_loop; + + // Verify we have received and logged an impression for the origin trial. + conversion_manager->GetActiveImpressionsForWebUI(base::BindLambdaForTesting( + [&](std::vector<StorableImpression> impressions) -> void { + EXPECT_EQ(1u, impressions.size()); + run_loop.Quit(); + })); + run_loop.Run(); +} + +// TODO(johnidel): Add tests that exercise the conversion side logic as well. +// This requires also using an embedded test server because the +// UrlLoadInterceptor cannot properly redirect the conversion pings. + +} // namespace content diff --git a/chromium/content/browser/conversions/impression_declaration_browsertest.cc b/chromium/content/browser/conversions/impression_declaration_browsertest.cc index 9e12221ca75..c593b0cf956 100644 --- a/chromium/content/browser/conversions/impression_declaration_browsertest.cc +++ b/chromium/content/browser/conversions/impression_declaration_browsertest.cc @@ -9,13 +9,17 @@ #include "base/run_loop.h" #include "base/test/scoped_feature_list.h" #include "base/time/time.h" +#include "build/build_config.h" +#include "content/browser/conversions/conversion_manager_impl.h" #include "content/browser/web_contents/web_contents_impl.h" #include "content/public/browser/navigation_handle.h" #include "content/public/common/content_features.h" +#include "content/public/common/content_switches.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_browser_test.h" #include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/test_navigation_observer.h" #include "content/shell/browser/shell.h" #include "net/dns/mock_host_resolver.h" #include "net/test/embedded_test_server/default_handlers.h" @@ -26,13 +30,15 @@ namespace content { // WebContentsObserver that waits until an impression is available on a // navigation handle for a finished navigation. -class ImpressionObserver : public WebContentsObserver { +class ImpressionObserver : public TestNavigationObserver { public: - explicit ImpressionObserver(WebContents* contents) - : WebContentsObserver(contents) {} + explicit ImpressionObserver(WebContents* contents, + size_t num_impressions = 1u) + : TestNavigationObserver(contents), + expected_num_impressions_(num_impressions) {} // WebContentsObserver - void DidFinishNavigation(NavigationHandle* navigation_handle) override { + void OnDidFinishNavigation(NavigationHandle* navigation_handle) override { if (!navigation_handle->GetImpression()) { if (waiting_for_null_impression_) impression_loop_.Quit(); @@ -40,14 +46,21 @@ class ImpressionObserver : public WebContentsObserver { } last_impression_ = *(navigation_handle->GetImpression()); + num_impressions_++; - if (!waiting_for_null_impression_) + if (!waiting_for_null_impression_ && + num_impressions_ >= expected_num_impressions_) { impression_loop_.Quit(); + } } const Impression& last_impression() { return *last_impression_; } - const Impression& WaitForImpression() { + // Waits for |expected_num_impressions_| navigations with impressions, and + // returns the last impression. + const Impression& Wait() { + if (num_impressions_ >= expected_num_impressions_) + return *last_impression_; impression_loop_.Run(); return last_impression(); } @@ -60,15 +73,18 @@ class ImpressionObserver : public WebContentsObserver { } private: + size_t num_impressions_ = 0u; + const size_t expected_num_impressions_ = 0u; base::Optional<Impression> last_impression_; bool waiting_for_null_impression_ = false; base::RunLoop impression_loop_; }; -class ImpressionDeclarationBrowserTest : public ContentBrowserTest { +class ImpressionDisabledBrowserTest : public ContentBrowserTest { public: - ImpressionDeclarationBrowserTest() { + ImpressionDisabledBrowserTest() { feature_list_.InitAndEnableFeature(features::kConversionMeasurement); + ConversionManagerImpl::RunInMemoryForTesting(); } void SetUpOnMainThread() override { @@ -99,6 +115,37 @@ class ImpressionDeclarationBrowserTest : public ContentBrowserTest { std::unique_ptr<net::EmbeddedTestServer> https_server_; }; +// Verifies that impressions are not logged when the Runtime feature isn't +// enabled. +IN_PROC_BROWSER_TEST_F(ImpressionDisabledBrowserTest, + ImpressionWithoutFeatureEnabled_NotReceived) { + ImpressionObserver impression_observer(web_contents()); + EXPECT_TRUE(NavigateToURL( + web_contents(), + https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); + + EXPECT_TRUE(ExecJs(web_contents(), R"( + createImpressionTag("link", + "page_with_conversion_redirect.html", + "1" /* impression data */, + "https://a.com" /* conversion_destination */);)")); + EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); + + // No impression should be observed. + EXPECT_TRUE(impression_observer.WaitForNavigationWithNoImpression()); +} + +class ImpressionDeclarationBrowserTest : public ImpressionDisabledBrowserTest { + public: + ImpressionDeclarationBrowserTest() = default; + + // Sets up the blink runtime feature for ConversionMeasurement. + void SetUpCommandLine(base::CommandLine* command_line) override { + command_line->AppendSwitch( + switches::kEnableExperimentalWebPlatformFeatures); + } +}; + IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, ImpressionTagClicked_ImpressionReceived) { ImpressionObserver impression_observer(web_contents()); @@ -118,7 +165,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); // Wait for the impression to be seen by the observer. - Impression last_impression = impression_observer.WaitForImpression(); + Impression last_impression = impression_observer.Wait(); // Verify the attributes of the impression are set as expected. EXPECT_EQ(1UL, last_impression.impression_data); @@ -135,8 +182,6 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, web_contents(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); - ShellAddedObserver new_shell_observer; - // Create an impression tag with a target frame that does not exist, which // will open a new window to navigate. EXPECT_TRUE(ExecJs(web_contents(), R"( @@ -145,20 +190,27 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, "1" /* impression data */, "https://a.com" /* conversion_destination */, "target" /* target */);)")); - EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); - ImpressionObserver impression_observer( - new_shell_observer.GetShell()->web_contents()); + ImpressionObserver impression_observer(nullptr); + impression_observer.StartWatchingNewWebContents(); + EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); // Wait for the impression to be seen by the observer. - Impression last_impression = impression_observer.WaitForImpression(); + Impression last_impression = impression_observer.Wait(); EXPECT_EQ(1UL, impression_observer.last_impression().impression_data); } -// Test frequently flakes due to timeout. ( https://crbug.com/1084201 ) +// Flaky on Mac: crbug.com/1099410 +#if defined(OS_MACOSX) +#define MAYBE_ImpressionTagNavigatesExistingRemoteFrame_ImpressionReceived \ + DISABLED_ImpressionTagNavigatesExistingRemoteFrame_ImpressionReceived +#else +#define MAYBE_ImpressionTagNavigatesExistingRemoteFrame_ImpressionReceived \ + ImpressionTagNavigatesExistingRemoteFrame_ImpressionReceived +#endif IN_PROC_BROWSER_TEST_F( ImpressionDeclarationBrowserTest, - DISABLED_ImpressionTagNavigatesExistingRemoteFrame_ImpressionReceived) { + MAYBE_ImpressionTagNavigatesExistingRemoteFrame_ImpressionReceived) { EXPECT_TRUE(NavigateToURL( web_contents(), https_server()->GetURL("b.test", "/page_with_impression_creator.html"))); @@ -181,12 +233,12 @@ IN_PROC_BROWSER_TEST_F( "1" /* impression data */, "https://a.com" /* conversion_destination */, "target" /* target */);)")); - EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); ImpressionObserver impression_observer(remote_web_contents); + EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); // Wait for the impression to be seen by the observer. - Impression last_impression = impression_observer.WaitForImpression(); + Impression last_impression = impression_observer.Wait(); EXPECT_EQ(1UL, impression_observer.last_impression().impression_data); } @@ -207,7 +259,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); // Wait for the impression to be seen by the observer. - Impression last_impression = impression_observer.WaitForImpression(); + Impression last_impression = impression_observer.Wait(); EXPECT_EQ(0UL, impression_observer.last_impression().impression_data); } @@ -218,8 +270,6 @@ IN_PROC_BROWSER_TEST_F( https_server()->GetURL("b.test", "/page_with_impression_creator.html"); EXPECT_TRUE(NavigateToURL(web_contents(), page_url)); - ShellAddedObserver new_shell_observer; - // Create an impression tag that is opened via middle click. This navigates in // a new WebContents. EXPECT_TRUE(ExecJs(web_contents(), R"( @@ -227,12 +277,12 @@ IN_PROC_BROWSER_TEST_F( "page_with_conversion_redirect.html", "1" /* impression data */, "https://a.com" /* conversion_destination */);)")); - EXPECT_TRUE(ExecJs(shell(), "simulateMiddleClick(\'link\');")); - ImpressionObserver impression_observer( - new_shell_observer.GetShell()->web_contents()); + ImpressionObserver impression_observer(nullptr); + impression_observer.StartWatchingNewWebContents(); + EXPECT_TRUE(ExecJs(shell(), "simulateMiddleClick(\'link\');")); - Impression last_impression = impression_observer.WaitForImpression(); + Impression last_impression = impression_observer.Wait(); // Verify the attributes of the impression are set as expected. EXPECT_EQ(1UL, last_impression.impression_data); @@ -260,12 +310,13 @@ IN_PROC_BROWSER_TEST_F( link.addEventListener('focus', function() { document.title = 'focused'; }); link.focus();)")); EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); + + ImpressionObserver impression_observer(web_contents()); content::SimulateKeyPress(web_contents(), ui::DomKey::ENTER, ui::DomCode::ENTER, ui::VKEY_RETURN, false, false, false, false); - ImpressionObserver impression_observer(web_contents()); - Impression last_impression = impression_observer.WaitForImpression(); + Impression last_impression = impression_observer.Wait(); // Verify the attributes of the impression are set as expected. EXPECT_EQ(1UL, last_impression.impression_data); @@ -393,7 +444,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, EXPECT_TRUE(ExecJs(subframe, "simulateClick('link');")); // We should see a null impression on the navigation - EXPECT_EQ(1u, impression_observer.WaitForImpression().impression_data); + EXPECT_EQ(1u, impression_observer.Wait().impression_data); } IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, @@ -409,7 +460,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, "1" /* impression data */, "https://a.com" /* conversion_destination */);)")); EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); - EXPECT_EQ(1UL, impression_observer.WaitForImpression().impression_data); + EXPECT_EQ(1UL, impression_observer.Wait().impression_data); ImpressionObserver reload_observer(web_contents()); shell()->Reload(); @@ -432,7 +483,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, "1" /* impression data */, "https://a.com" /* conversion_destination */);)")); EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'link\');")); - EXPECT_EQ(1UL, impression_observer.WaitForImpression().impression_data); + EXPECT_EQ(1UL, impression_observer.Wait().impression_data); ImpressionObserver reload_observer(web_contents()); EXPECT_TRUE(ExecJs(web_contents(), "window.location.reload()")); @@ -451,7 +502,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, // Click the default impression on the page. EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'impression_tag\');")); - EXPECT_EQ(1UL, impression_observer.WaitForImpression().impression_data); + EXPECT_EQ(1UL, impression_observer.Wait().impression_data); // Navigate away so we can back navigate to the impression's navigated page. EXPECT_TRUE(NavigateToURL(web_contents(), GURL("about:blank"))); @@ -471,8 +522,7 @@ IN_PROC_BROWSER_TEST_F(ImpressionDeclarationBrowserTest, WaitForLoadStop(web_contents()); ImpressionObserver second_impression_observer(web_contents()); EXPECT_TRUE(ExecJs(shell(), "simulateClick(\'impression_tag\');")); - EXPECT_EQ(1UL, - second_impression_observer.WaitForImpression().impression_data); + EXPECT_EQ(1UL, second_impression_observer.Wait().impression_data); } } // namespace content |