diff --git a/browser/brave_ads/BUILD.gn b/browser/brave_ads/BUILD.gn index 139d18045f9d..02acaa00427f 100644 --- a/browser/brave_ads/BUILD.gn +++ b/browser/brave_ads/BUILD.gn @@ -144,6 +144,8 @@ source_set("browser_tests") { "analytics/p3a/brave_stats_helper_browsertest.cc", "application_state/notification_helper/notification_helper_impl_mock.cc", "application_state/notification_helper/notification_helper_impl_mock.h", + "test_ads_service_waiter.cc", + "test_ads_service_waiter.h", ] deps = [ diff --git a/browser/brave_ads/analytics/p3a/brave_stats_helper_browsertest.cc b/browser/brave_ads/analytics/p3a/brave_stats_helper_browsertest.cc index 02aa8028f477..514acbaa4592 100644 --- a/browser/brave_ads/analytics/p3a/brave_stats_helper_browsertest.cc +++ b/browser/brave_ads/analytics/p3a/brave_stats_helper_browsertest.cc @@ -10,7 +10,7 @@ #include "base/test/metrics/histogram_tester.h" #include "brave/browser/brave_browser_process.h" #include "brave/components/brave_ads/core/public/prefs/pref_names.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" diff --git a/browser/brave_ads/application_state/background_helper/background_helper.cc b/browser/brave_ads/application_state/background_helper/background_helper.cc index 8802acc949c2..e0d76540afc2 100644 --- a/browser/brave_ads/application_state/background_helper/background_helper.cc +++ b/browser/brave_ads/application_state/background_helper/background_helper.cc @@ -6,7 +6,7 @@ #include "brave/components/brave_ads/browser/application_state/background_helper.h" #include "brave/browser/brave_ads/application_state/background_helper/background_helper_holder.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" namespace brave_ads { diff --git a/browser/brave_ads/application_state/background_helper/background_helper_holder.cc b/browser/brave_ads/application_state/background_helper/background_helper_holder.cc index 300a7d2ad7af..c5cb6e909682 100644 --- a/browser/brave_ads/application_state/background_helper/background_helper_holder.cc +++ b/browser/brave_ads/application_state/background_helper/background_helper_holder.cc @@ -7,7 +7,7 @@ #include "base/no_destructor.h" #include "brave/components/brave_ads/browser/application_state/background_helper.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #if BUILDFLAG(IS_ANDROID) #include "brave/browser/brave_ads/application_state/background_helper/background_helper_android.h" diff --git a/browser/brave_ads/application_state/notification_helper/notification_helper.cc b/browser/brave_ads/application_state/notification_helper/notification_helper.cc index 39e75efac64b..fe50782a1036 100644 --- a/browser/brave_ads/application_state/notification_helper/notification_helper.cc +++ b/browser/brave_ads/application_state/notification_helper/notification_helper.cc @@ -8,7 +8,7 @@ #include "base/functional/bind.h" #include "base/no_destructor.h" #include "brave/browser/brave_ads/application_state/notification_helper/notification_helper_impl.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/notifications/notification_platform_bridge.h" #include "chrome/browser/profiles/profile.h" diff --git a/browser/brave_ads/tabs/BUILD.gn b/browser/brave_ads/tabs/BUILD.gn index a13e89679af2..d9ca0686dc8d 100644 --- a/browser/brave_ads/tabs/BUILD.gn +++ b/browser/brave_ads/tabs/BUILD.gn @@ -35,22 +35,21 @@ source_set("tabs") { source_set("browser_tests") { testonly = true - sources = [ "ads_tab_helper_browsertest.cc" ] - - deps = [ - ":tabs", - "//brave/components/brave_ads/browser:test_support", - "//brave/components/brave_ads/core/public:headers", - "//brave/components/brave_rewards/common", - "//chrome/browser/ui", - "//net:test_support", - ] - - if (is_android) { - deps += [ "//chrome/test:test_support_ui_android" ] - } else { - deps += [ "//chrome/test:test_support_ui" ] + if (!is_android) { + sources = [ "ads_tab_helper_browsertest.cc" ] + + deps = [ + ":tabs", + "//brave/browser/brave_ads", + "//brave/components/brave_ads/browser:test_support", + "//brave/components/brave_ads/core/public:headers", + "//brave/components/brave_rewards/common", + "//chrome/browser/ui", + "//chrome/test:test_support", + "//chrome/test:test_support_ui", + "//net:test_support", + ] + + defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] } - - defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] } diff --git a/browser/brave_ads/tabs/ads_tab_helper.cc b/browser/brave_ads/tabs/ads_tab_helper.cc index a92b56bcfb22..e0dd6953228a 100644 --- a/browser/brave_ads/tabs/ads_tab_helper.cc +++ b/browser/brave_ads/tabs/ads_tab_helper.cc @@ -5,6 +5,7 @@ #include "brave/browser/brave_ads/tabs/ads_tab_helper.h" +#include "base/check.h" #include "base/check_is_test.h" #include "base/containers/contains.h" #include "base/strings/stringprintf.h" @@ -17,10 +18,12 @@ #include "components/prefs/pref_service.h" #include "components/sessions/content/session_tab_helper.h" #include "components/sessions/core/session_id.h" +#include "content/public/browser/media_player_id.h" #include "content/public/browser/navigation_handle.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "net/http/http_response_headers.h" +#include "net/http/http_status_code.h" #include "ui/base/page_transition_types.h" #if !BUILDFLAG(IS_ANDROID) @@ -32,8 +35,8 @@ namespace brave_ads { namespace { -constexpr int kHtmlClientErrorResponseCodeClass = 4; -constexpr int kHtmlServerErrorResponseCodeClass = 5; +constexpr int kHttpClientErrorResponseStatusCodeClass = 4; +constexpr int kHttpServerErrorResponseStatusCodeClass = 5; constexpr char16_t kSerializeDocumentToStringJavaScript[] = u"new XMLSerializer().serializeToString(document)"; @@ -49,12 +52,11 @@ std::string MediaPlayerUuid(const content::MediaPlayerId& id) { } // namespace -AdsTabHelper::AdsTabHelper(content::WebContents* web_contents) +AdsTabHelper::AdsTabHelper(content::WebContents* const web_contents) : content::WebContentsObserver(web_contents), content::WebContentsUserData(*web_contents), session_id_(sessions::SessionTabHelper::IdForTab(web_contents)) { if (!session_id_.is_valid()) { - // Invalid session id instance. return; } @@ -62,7 +64,6 @@ AdsTabHelper::AdsTabHelper(content::WebContents* web_contents) Profile::FromBrowserContext(web_contents->GetBrowserContext()); ads_service_ = AdsServiceFactory::GetForProfile(profile); if (!ads_service_) { - // No-op if the ads service is unavailable. return; } @@ -82,7 +83,7 @@ AdsTabHelper::~AdsTabHelper() { #endif } -void AdsTabHelper::SetAdsServiceForTesting(AdsService* ads_service) { +void AdsTabHelper::SetAdsServiceForTesting(AdsService* const ads_service) { CHECK_IS_TEST(); ads_service_ = ads_service; @@ -106,6 +107,7 @@ bool AdsTabHelper::UserHasOptedInToNotificationAds() const { } bool AdsTabHelper::IsVisible() const { + // The web contents must be visible and the browser must be active. return is_web_contents_visible_ && is_browser_active_.value_or(false); } @@ -140,49 +142,45 @@ void AdsTabHelper::MaybeSetBrowserIsNoLongerActive() { } bool AdsTabHelper::IsNewNavigation( - content::NavigationHandle* navigation_handle) { + content::NavigationHandle* const navigation_handle) { CHECK(navigation_handle); return ui::PageTransitionIsNewNavigation( navigation_handle->GetPageTransition()); } -bool AdsTabHelper::IsErrorPage(content::NavigationHandle* navigation_handle) { +std::optional AdsTabHelper::HttpStatusCode( + content::NavigationHandle* const navigation_handle) { CHECK(navigation_handle); - // Only consider client and server error responses as error pages. if (const net::HttpResponseHeaders* const response_headers = navigation_handle->GetResponseHeaders()) { - const int response_code_class = response_headers->response_code() / 100; - return response_code_class == kHtmlClientErrorResponseCodeClass || - response_code_class == kHtmlServerErrorResponseCodeClass; + return response_headers->response_code(); } - return false; + return std::nullopt; +} + +bool AdsTabHelper::IsErrorPage(const int http_status_code) const { + const int http_status_code_class = http_status_code / 100; + return http_status_code_class == kHttpClientErrorResponseStatusCodeClass || + http_status_code_class == kHttpServerErrorResponseStatusCodeClass; } void AdsTabHelper::ProcessNavigation() { MaybeNotifyTabHtmlContentDidChange(); MaybeNotifyTabTextContentDidChange(); - - // Set `was_restored_` to `false` so that listeners are notified of tab - // changes after the tab is restored. - was_restored_ = false; } void AdsTabHelper::ProcessSameDocumentNavigation() { MaybeNotifyTabHtmlContentDidChange(); - - // Set `was_restored_` to `false` so that listeners are notified of tab - // changes after the tab is restored. - was_restored_ = false; } void AdsTabHelper::ResetNavigationState() { redirect_chain_.clear(); redirect_chain_.shrink_to_fit(); - is_error_page_ = false; + http_status_code_.reset(); media_players_.clear(); } @@ -200,11 +198,10 @@ void AdsTabHelper::MaybeNotifyBrowserDidResignActive() { } void AdsTabHelper::MaybeNotifyUserGestureEventTriggered( - content::NavigationHandle* navigation_handle) { + content::NavigationHandle* const navigation_handle) { CHECK(navigation_handle); if (!ads_service_) { - // No-op if the ads service is unavailable. return; } @@ -227,7 +224,6 @@ void AdsTabHelper::MaybeNotifyUserGestureEventTriggered( void AdsTabHelper::MaybeNotifyTabDidChange() { if (!ads_service_) { - // No-op if the ads service is unavailable. return; } @@ -239,15 +235,30 @@ void AdsTabHelper::MaybeNotifyTabDidChange() { ads_service_->NotifyTabDidChange(/*tab_id=*/session_id_.id(), redirect_chain_, is_new_navigation_, was_restored_, - is_error_page_, IsVisible()); + IsVisible()); +} + +void AdsTabHelper::MaybeNotifyTabDidLoad() { + CHECK(http_status_code_); + + if (!ads_service_) { + // No-op if the ads service is unavailable. + return; + } + + ads_service_->NotifyTabDidLoad(/*tab_id=*/session_id_.id(), + *http_status_code_); } bool AdsTabHelper::ShouldNotifyTabContentDidChange() const { // Don't notify about content changes if the ads service is not available, the // tab was restored, was a previously committed navigation, the web contents - // are still loading, or an error page was displayed. + // are still loading, or an error page was displayed. `http_status_code_` can + // be `std::nullopt` if the navigation never finishes which can occur if the + // user constantly refreshes the page. return ads_service_ && !was_restored_ && is_new_navigation_ && - !redirect_chain_.empty() && !is_error_page_; + !redirect_chain_.empty() && http_status_code_ && + !IsErrorPage(*http_status_code_); } void AdsTabHelper::MaybeNotifyTabHtmlContentDidChange() { @@ -260,8 +271,7 @@ void AdsTabHelper::MaybeNotifyTabHtmlContentDidChange() { // for Brave Rewards users. However, we must notify that the tab content has // changed with empty HTML to ensure that regular conversions are processed. return ads_service_->NotifyTabHtmlContentDidChange( - /*tab_id=*/session_id_.id(), redirect_chain_, - /*html=*/""); + /*tab_id=*/session_id_.id(), redirect_chain_, /*html=*/""); } // Only utilized for verifiable conversions, which requires the user to have @@ -329,7 +339,12 @@ void AdsTabHelper::MaybeNotifyTabdidClose() { void AdsTabHelper::DidStartNavigation( content::NavigationHandle* navigation_handle) { - if (!ads_service_ || !navigation_handle->IsInPrimaryMainFrame()) { + if (!ads_service_) { + // No-op if the ads service is unavailable. + return; + } + + if (!navigation_handle->IsInPrimaryMainFrame()) { return; } @@ -341,10 +356,12 @@ void AdsTabHelper::DidStartNavigation( ResetNavigationState(); } +// This method is called when a navigation in the main frame or a subframe has +// completed. It indicates that the navigation has finished, but the document +// might still be loading resources. void AdsTabHelper::DidFinishNavigation( content::NavigationHandle* navigation_handle) { if (!ads_service_) { - // No-op if the ads service is unavailable. return; } @@ -355,28 +372,43 @@ void AdsTabHelper::DidFinishNavigation( redirect_chain_ = navigation_handle->GetRedirectChain(); - is_error_page_ = IsErrorPage(navigation_handle); + http_status_code_ = HttpStatusCode(navigation_handle).value_or(net::HTTP_OK); MaybeNotifyUserGestureEventTriggered(navigation_handle); + // Notify of tab changes after navigation completes but before notifying that + // the tab has loaded, so that any listeners can process the tab changes + // before the tab is considered loaded. MaybeNotifyTabDidChange(); + MaybeNotifyTabDidLoad(); + // Process same document navigations only when a document load is completed. // For navigations that lead to a document change, `ProcessNavigation` is // called from `DocumentOnLoadCompletedInPrimaryMainFrame`. if (navigation_handle->IsSameDocument() && web_contents()->IsDocumentOnLoadCompletedInPrimaryMainFrame()) { ProcessSameDocumentNavigation(); + + // Set `was_restored_` to `false` so that listeners are notified of tab + // changes after the tab is restored. + was_restored_ = false; } } +// This method is called when the document's onload event has fired in the +// primary main frame. This means that the document and all its subresources +// have finished loading. void AdsTabHelper::DocumentOnLoadCompletedInPrimaryMainFrame() { if (!ads_service_) { - // No-op if the ads service is unavailable. return; } ProcessNavigation(); + + // Set `was_restored_` to `false` so that listeners are notified of tab + // changes after the tab is restored. + was_restored_ = false; } bool AdsTabHelper::IsPlayingMedia(const std::string& media_player_uuid) { diff --git a/browser/brave_ads/tabs/ads_tab_helper.h b/browser/brave_ads/tabs/ads_tab_helper.h index 2fe92dd4d988..b3e053b1a79a 100644 --- a/browser/brave_ads/tabs/ads_tab_helper.h +++ b/browser/brave_ads/tabs/ads_tab_helper.h @@ -14,9 +14,8 @@ #include "base/memory/raw_ptr.h" #include "base/memory/weak_ptr.h" #include "base/values.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #include "components/sessions/core/session_id.h" -#include "content/public/browser/media_player_id.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" @@ -27,6 +26,10 @@ class Browser; class GURL; +namespace content { +struct MediaPlayerId; +} // namespace content + namespace brave_ads { class AdsService; @@ -37,7 +40,7 @@ class AdsTabHelper : public content::WebContentsObserver, #endif public content::WebContentsUserData { public: - explicit AdsTabHelper(content::WebContents*); + explicit AdsTabHelper(content::WebContents* const); ~AdsTabHelper() override; AdsTabHelper(const AdsTabHelper&) = delete; @@ -45,7 +48,7 @@ class AdsTabHelper : public content::WebContentsObserver, AdsService* ads_service() { return ads_service_; } - void SetAdsServiceForTesting(AdsService* ads_service); + void SetAdsServiceForTesting(AdsService* const ads_service); private: friend class content::WebContentsUserData; @@ -60,11 +63,14 @@ class AdsTabHelper : public content::WebContentsObserver, // Returns 'false' if the navigation was a back/forward navigation or a // reload, otherwise 'true'. - bool IsNewNavigation(content::NavigationHandle* navigation_handle); + bool IsNewNavigation(content::NavigationHandle* const navigation_handle); + + // NOTE: DO NOT use this method before the navigation commit as it will return + // null. It is safe to use from `WebContentsObserver::DidFinishNavigation()`. + std::optional HttpStatusCode( + content::NavigationHandle* const navigation_handle); - // DO NOT use this before the navigation commit. It would always return false. - // You can use it from WebContentsObserver::DidFinishNavigation(). - bool IsErrorPage(content::NavigationHandle* navigation_handle); + bool IsErrorPage(int http_status_code) const; void ProcessNavigation(); void ProcessSameDocumentNavigation(); @@ -74,10 +80,12 @@ class AdsTabHelper : public content::WebContentsObserver, void MaybeNotifyBrowserDidResignActive(); void MaybeNotifyUserGestureEventTriggered( - content::NavigationHandle* navigation_handle); + content::NavigationHandle* const navigation_handle); void MaybeNotifyTabDidChange(); + void MaybeNotifyTabDidLoad(); + bool ShouldNotifyTabContentDidChange() const; void MaybeNotifyTabHtmlContentDidChange(); void OnMaybeNotifyTabHtmlContentDidChange( @@ -124,7 +132,7 @@ class AdsTabHelper : public content::WebContentsObserver, bool was_restored_ = false; bool is_new_navigation_ = false; std::vector redirect_chain_; - bool is_error_page_ = false; + std::optional http_status_code_; std::set media_players_; diff --git a/browser/brave_ads/tabs/ads_tab_helper_browsertest.cc b/browser/brave_ads/tabs/ads_tab_helper_browsertest.cc index b01e7d8bf48b..4cdadab70b6c 100644 --- a/browser/brave_ads/tabs/ads_tab_helper_browsertest.cc +++ b/browser/brave_ads/tabs/ads_tab_helper_browsertest.cc @@ -5,249 +5,931 @@ #include "brave/browser/brave_ads/tabs/ads_tab_helper.h" +#include +#include +#include +#include +#include + +#include "base/callback_list.h" +#include "base/check.h" +#include "base/files/file_path.h" +#include "base/functional/bind.h" +#include "base/memory/raw_ptr.h" +#include "base/notreached.h" #include "base/path_service.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/stringprintf.h" #include "base/test/gmock_callback_support.h" +#include "brave/browser/brave_ads/ads_service_factory.h" #include "brave/components/brave_ads/browser/ads_service_mock.h" #include "brave/components/brave_ads/core/public/prefs/pref_names.h" #include "brave/components/brave_rewards/common/pref_names.h" #include "brave/components/constants/brave_paths.h" +#include "build/build_config.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" +#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/ssl/cert_verifier_browser_test.h" +#include "chrome/browser/sessions/session_restore_test_helper.h" +#include "chrome/browser/sessions/session_restore_test_utils.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_commands.h" +#include "chrome/browser/ui/browser_tabstrip.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/chrome_test_utils.h" +#include "chrome/test/base/in_process_browser_test.h" +#include "chrome/test/base/platform_browser_test.h" #include "chrome/test/base/ui_test_utils.h" +#include "components/keep_alive_registry/keep_alive_types.h" +#include "components/keep_alive_registry/scoped_keep_alive.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/keyed_service/core/keyed_service.h" #include "components/prefs/pref_service.h" +#include "components/sessions/content/session_tab_helper.h" +#include "components/sessions/core/session_id.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/media_player_id.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_observer.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/content_mock_cert_verifier.h" #include "net/dns/mock_host_resolver.h" +#include "net/http/http_status_code.h" #include "net/test/embedded_test_server/embedded_test_server.h" +#include "net/test/embedded_test_server/http_request.h" +#include "net/test/embedded_test_server/http_response.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" -// npm run test -- brave_browser_tests --filter=AdsTabHelperTest* +// npm run test -- brave_browser_tests --filter=BraveAds* namespace brave_ads { namespace { -using base::test::RunClosure; -using testing::_; -using testing::IsEmpty; -using testing::Not; - -constexpr char kUrlDomain[] = "example.com"; -constexpr char kUrlPath[] = "/brave_ads/basic_page.html"; -constexpr char kSinglePageApplicationUrlPath[] = +constexpr char kHostName[] = "brave.com"; + +constexpr char kHandleRequestUrlPath[] = "/handle_request"; +constexpr char kHttpStatusCodeQueryKey[] = "http_status_code"; + +constexpr char kMultiPageApplicationWebpage[] = + "/brave_ads/multi_page_application.html"; +constexpr char kMultiPageApplicationWebpageHtmlContent[] = + "\n Adventure " + "Awaits\n\n\n\n

Welcome to Your Adventure

\n " + "

\n Embark on a journey of learning and discovery. Each step you " + "take brings you closer to mastering new skills and\n achieving your " + "goals.\n

\n \n " + "
\n \"The only limit to our realization of tomorrow is our " + "doubts of today.\" - Franklin D. Roosevelt\n
\n \n \n \n " + "\n \n \n \n " + "\n \n \n \n \n \n \n " + "\n \n \n " + "
TaskStatus
Learn RustCompleted
Contribute to a GitHub " + "repositoryIn Progress
Build a mobile appPending
\n\n\n\n"; +constexpr char kMultiPageApplicationWebpageTextContent[] = + "Welcome to Your Adventure\n\nEmbark on a journey of learning and " + "discovery. Each step you take brings you closer to mastering new skills " + "and achieving your goals.\n\nExplore new programming " + "languages\nContribute to open-source projects\nDevelop innovative " + "applications\n\"The only limit to our realization of tomorrow is our " + "doubts of today.\" - Franklin D. Roosevelt\nTask\tStatus\nLearn " + "Rust\tCompleted\nContribute to a GitHub repository\tIn Progress\nBuild a " + "mobile app\tPending"; + +constexpr char kSinglePageApplicationWebpage[] = "/brave_ads/single_page_application.html"; -constexpr char k500ErrorPagePath[] = "/500_error_page.html"; -constexpr char k404ErrorPagePath[] = "/404_error_page.html"; - -AdsTabHelper* GetActiveAdsTabHelper(Browser* browser) { - auto* web_contents = browser->tab_strip_model()->GetActiveWebContents(); - return AdsTabHelper::FromWebContents(web_contents); +constexpr char kSinglePageApplicationWebpageHtmlContent[] = + "\n Single Page Application\n " + "\n\n\n\n

same_document

\n \n\n\n\n"; +constexpr char kSinglePageApplicationClickSelectors[] = + "[data-navigation-type='same_document']"; + +constexpr char kAutoplayVideoWebpage[] = "/brave_ads/autoplay_video.html"; +constexpr char kVideoWebpage[] = "/brave_ads/video.html"; +constexpr char kVideoJavascriptDocumentQuerySelectors[] = "video"; + +MATCHER_P(FileName, filename, "") { + return arg.ExtractFileName() == filename; } +class MediaWaiter : public content::WebContentsObserver { + public: + explicit MediaWaiter(content::WebContents* const web_contents) + : content::WebContentsObserver(web_contents) {} + + void WaitForMediaStartedPlaying() { media_started_playing_run_loop_.Run(); } + + void WaitForMediaDestroyed() { media_destroyed_run_loop_.Run(); } + + void WaitForMediaSessionCreated() { media_session_created_run_loop_.Run(); } + + // content::WebContentsObserver: + void MediaStartedPlaying(const MediaPlayerInfo& /*video_type*/, + const content::MediaPlayerId& id) override { + id_ = id; + media_started_playing_run_loop_.Quit(); + } + + void MediaDestroyed(const content::MediaPlayerId& id) override { + EXPECT_EQ(id, id_); + media_destroyed_run_loop_.Quit(); + } + + void MediaSessionCreated( + content::MediaSession* const /*media_session*/) override { + media_session_created_run_loop_.Quit(); + } + + private: + std::optional id_; + + base::RunLoop media_started_playing_run_loop_; + base::RunLoop media_destroyed_run_loop_; + base::RunLoop media_session_created_run_loop_; +}; + } // namespace -class AdsTabHelperTest : public CertVerifierBrowserTest { +// We expect `is_visible=true` if both the browser and tab are active, and +// `is_visible=false` if either the browser or tab is inactive. To avoid flaky +// tests caused by the browser becoming inactive, we match on `::testing::_`. + +class BraveAdsTabHelperTest : public PlatformBrowserTest { public: void SetUpOnMainThread() override { - CertVerifierBrowserTest::SetUpOnMainThread(); - mock_cert_verifier()->set_default_result(net::OK); + PlatformBrowserTest::SetUpOnMainThread(); + + mock_cert_verifier_.mock_cert_verifier()->set_default_result(net::OK); host_resolver()->AddRule("*", "127.0.0.1"); + InitEmbeddedTestServer(); + } + + void SetUpCommandLine(base::CommandLine* command_line) override { + PlatformBrowserTest::SetUpCommandLine(command_line); + + mock_cert_verifier_.SetUpCommandLine(command_line); + } + + void SetUpInProcessBrowserTestFixture() override { + PlatformBrowserTest::SetUpInProcessBrowserTestFixture(); + + mock_cert_verifier_.SetUpInProcessBrowserTestFixture(); + + callback_list_subscription_ = + BrowserContextDependencyManager::GetInstance() + ->RegisterCreateServicesCallbackForTesting(base::BindRepeating( + &BraveAdsTabHelperTest::OnWillCreateBrowserContextServices, + base::Unretained(this))); + } + + void TearDownInProcessBrowserTestFixture() override { + mock_cert_verifier_.TearDownInProcessBrowserTestFixture(); + + PlatformBrowserTest::TearDownInProcessBrowserTestFixture(); + } + + void OnWillCreateBrowserContextServices( + content::BrowserContext* const context) { + AdsServiceFactory::GetInstance()->SetTestingFactory( + context, base::BindRepeating(&BraveAdsTabHelperTest::CreateAdsService, + base::Unretained(this))); + } + + std::unique_ptr CreateAdsService( + content::BrowserContext* const /*context*/) { + // Since we are mocking the `AdsService`, a delegate is not required. Note + // that we are not testing the `AdsService` itself, these tests are focused + // on the `AdsTabHelper`. + auto ads_service = std::make_unique(/*delegate*/ nullptr); + ads_service_mock_ = ads_service.get(); + return ads_service; + } + + AdsServiceMock& ads_service_mock() { return *ads_service_mock_; } + + Profile* GetProfile() { return chrome_test_utils::GetProfile(this); } + + PrefService* GetPrefs() { return GetProfile()->GetPrefs(); } + + base::FilePath GetTestDataDir() { + const base::ScopedAllowBlockingForTesting scoped_allow_blocking; + return base::PathService::CheckedGet(brave::DIR_TEST_DATA); + } - const base::FilePath test_data_dir = - base::PathService::CheckedGet(brave::DIR_TEST_DATA); - https_server_.ServeFilesFromDirectory(test_data_dir); - https_server_.RegisterRequestHandler(base::BindRepeating( - &AdsTabHelperTest::HandleRequest, base::Unretained(this))); - ASSERT_TRUE(https_server_.Start()); + void InitEmbeddedTestServer() { + const base::FilePath test_data_dir = GetTestDataDir(); - GetActiveAdsTabHelper(browser())->SetAdsServiceForTesting(&ads_service()); + test_server_.ServeFilesFromDirectory(test_data_dir); + test_server_.RegisterRequestHandler(base::BindRepeating( + &BraveAdsTabHelperTest::HandleRequest, base::Unretained(this))); + test_server_handle_ = test_server_.StartAndReturnHandle(); + EXPECT_TRUE(test_server_handle_); } - PrefService* GetPrefs() { return browser()->profile()->GetPrefs(); } + int32_t TabId() { + content::WebContents* const web_contents = GetActiveWebContents(); + EXPECT_TRUE(web_contents); - net::EmbeddedTestServer& https_server() { return https_server_; } + return sessions::SessionTabHelper::IdForTab(web_contents).id(); + } + + content::WebContents* GetActiveWebContents() { + return chrome_test_utils::GetActiveWebContents(this); + } + + bool WaitForActiveWebContentsToLoad() { + content::WebContents* const web_contents = GetActiveWebContents(); + EXPECT_TRUE(web_contents); - AdsServiceMock& ads_service() { return ads_service_mock_; } + web_contents->GetController().LoadIfNecessary(); + + return content::WaitForLoadStop(web_contents); + } + + void CloseActiveWebContents() { + content::WebContents* const web_contents = GetActiveWebContents(); + chrome::CloseWebContents(browser(), web_contents, /*add_to_history=*/false); + } + + void NavigateToURL(const std::string_view relative_url, + const bool has_user_gesture) { + content::WebContents* const web_contents = GetActiveWebContents(); + EXPECT_TRUE(web_contents); + + const GURL url = test_server_.GetURL(kHostName, relative_url); + + content::NavigationController::LoadURLParams params(url); + if (has_user_gesture) { + return content::NavigateToURLBlockUntilNavigationsComplete( + web_contents, url, /*number_of_navigations=*/1, + /*ignore_uncommitted_navigations=*/true); + } + + EXPECT_TRUE(NavigateToURLFromRendererWithoutUserGesture(web_contents, url)); + } + + void SimulateHttpStatusCodePage(const int http_status_code) { + const std::string relative_url = + base::StringPrintf("%s?%s=%d", kHandleRequestUrlPath, + kHttpStatusCodeQueryKey, http_status_code); + NavigateToURL(relative_url, /*has_user_gesture=*/true); + } + + ::testing::AssertionResult ExecuteJavaScript(const std::string& javascript, + const bool has_user_gesture) { + content::WebContents* const web_contents = GetActiveWebContents(); + return content::ExecJs(web_contents, javascript, + has_user_gesture + ? content::EXECUTE_SCRIPT_DEFAULT_OPTIONS + : content::EXECUTE_SCRIPT_NO_USER_GESTURE); + } + + void GoBack() { + content::WebContents* const web_contents = GetActiveWebContents(); + EXPECT_TRUE(web_contents); + + content::NavigationController& navigation_controller = + web_contents->GetController(); + EXPECT_TRUE(navigation_controller.CanGoBack()); + navigation_controller.GoBack(); + } + + void GoForward() { + content::WebContents* const web_contents = GetActiveWebContents(); + EXPECT_TRUE(web_contents); + + content::NavigationController& navigation_controller = + web_contents->GetController(); + EXPECT_TRUE(navigation_controller.CanGoForward()); + navigation_controller.GoForward(); + } + + void Reload() { + content::WebContents* const web_contents = GetActiveWebContents(); + EXPECT_TRUE(web_contents); + + web_contents->GetController().Reload(content::ReloadType::NORMAL, + /*check_for_repost=*/false); + } + + void SimulateClick(const std::string& selectors, + const bool has_user_gesture) { + const std::string javascript = base::ReplaceStringPlaceholders( + R"(document.querySelector("$1").click();)", {selectors}, nullptr); + ExecuteJavaScript(javascript, has_user_gesture); + } + + void StartVideoPlayback(const std::string& selectors) { + const std::string javascript = base::ReplaceStringPlaceholders( + R"(document.querySelector("$1")?.play();)", {selectors}, nullptr); + + // Video elements must be executed with a user gesture. + ExecuteJavaScript(javascript, /*has_user_gesture=*/true); + } + + void PauseVideoPlayback(const std::string& selectors) { + const std::string javascript = base::ReplaceStringPlaceholders( + R"(document.querySelector("$1")?.pause();)", {selectors}, nullptr); + ExecuteJavaScript(javascript, /*has_user_gesture=*/true); + } + + void RestoreBrowser(Profile* const profile) { + CHECK(profile); + + SessionRestoreTestHelper session_restore_test_helper; + chrome::OpenWindowWithRestoredTabs(profile); + if (SessionRestore::IsRestoring(profile)) { + session_restore_test_helper.Wait(); + } + + SelectFirstBrowser(); + } + + std::unique_ptr HandleHttpStatusCodeQueryKey( + const std::string& value) const { + auto http_response = + std::make_unique(); + + int http_status_code_as_int; + EXPECT_TRUE(base::StringToInt(value, &http_status_code_as_int)); + const std::optional http_status_code = + net::TryToGetHttpStatusCode(http_status_code_as_int); + EXPECT_TRUE(http_status_code); + http_response->set_code(*http_status_code); + + http_response->set_content_type("text/html"); + const std::string http_status_code_page = base::StringPrintf( + R"( + + + + HTTP Status Code + + + + %d (%s) + + )", + *http_status_code, http_response->reason().c_str()); + http_response->set_content(http_status_code_page); + + return http_response; + } std::unique_ptr HandleRequest( - const net::test_server::HttpRequest& request) { - if (base::Contains(request.relative_url, k500ErrorPagePath)) { - // Return a 500 error without any content, causing the error page to be - // displayed. - auto response = std::make_unique(); - response->set_code(net::HTTP_INTERNAL_SERVER_ERROR); - return response; + const net::test_server::HttpRequest& http_request) const { + const GURL url = http_request.GetURL(); + if (url.path() != kHandleRequestUrlPath) { + // Do not handle the request. + return nullptr; } - if (base::Contains(request.relative_url, k404ErrorPagePath)) { - // Return a 404 error with HTML content. - auto response = std::make_unique(); - response->set_code(net::HTTP_NOT_FOUND); - response->set_content_type("text/html"); - response->set_content("Not Found"); - return response; + // Handle request. + base::StringPairs key_value_pairs; + base::SplitStringIntoKeyValuePairs(url.query(), '=', '&', &key_value_pairs); + + for (const auto& [key, value] : key_value_pairs) { + if (key == kHttpStatusCodeQueryKey) { + return HandleHttpStatusCodeQueryKey(value); + } } - return nullptr; + NOTREACHED_NORETURN() + << "Query key not found. Unable to handle the request."; + } + + std::vector RedirectChainExpectation( + const std::string_view relative_url) const { + const GURL url = test_server_.GetURL(kHostName, relative_url); + return {url}; } private: - net::EmbeddedTestServer https_server_{ + content::ContentMockCertVerifier mock_cert_verifier_; + + base::CallbackListSubscription callback_list_subscription_; + + raw_ptr ads_service_mock_ = nullptr; + + net::EmbeddedTestServer test_server_{ net::test_server::EmbeddedTestServer::TYPE_HTTPS}; - AdsServiceMock ads_service_mock_{nullptr}; + net::test_server::EmbeddedTestServerHandle test_server_handle_; }; -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, UserHasNotJoinedBraveRewards) { +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, NotifyTabDidChange) { + EXPECT_CALL( + ads_service_mock(), + NotifyTabDidChange(TabId(), + RedirectChainExpectation(kMultiPageApplicationWebpage), + /*is_new_navigation=*/true, /*is_restoring=*/false, + /*is_visible=*/::testing::_)) + .Times(::testing::AtLeast(1)); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidChangeIfTabWasRestored) { + EXPECT_CALL(ads_service_mock(), NotifyTabDidChange) + .Times(::testing::AnyNumber()); + + EXPECT_CALL( + ads_service_mock(), + NotifyTabDidChange(TabId(), + RedirectChainExpectation(kMultiPageApplicationWebpage), + /*is_new_navigation=*/true, /*is_restoring=*/false, + /*is_visible=*/::testing::_)) + .Times(::testing::AtLeast(1)); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + + // Must occur before the browser is closed. + Profile* const profile = GetProfile(); + + const ScopedKeepAlive scoped_keep_alive(KeepAliveOrigin::SESSION_RESTORE, + KeepAliveRestartOption::DISABLED); + const ScopedProfileKeepAlive scoped_profile_keep_alive( + profile, ProfileKeepAliveOrigin::kSessionRestore); + CloseBrowserSynchronously(browser()); + + // We do not know the tab id until the tab is restored, so we match on + // `::testing::_`. + EXPECT_CALL(ads_service_mock(), + NotifyTabDidChange( + /*tab_id=*/::testing::_, + RedirectChainExpectation(kMultiPageApplicationWebpage), + /*is_new_navigation=*/false, /*is_restoring=*/true, + /*is_visible=*/::testing::_)); + RestoreBrowser(profile); + + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, NotifyTabDidLoad) { + EXPECT_CALL(ads_service_mock(), NotifyTabDidLoad(TabId(), net::HTTP_OK)); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidLoadForHttpServerErrorResponsePage) { + EXPECT_CALL(ads_service_mock(), + NotifyTabDidLoad(TabId(), net::HTTP_INTERNAL_SERVER_ERROR)); + SimulateHttpStatusCodePage(net::HTTP_INTERNAL_SERVER_ERROR); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidLoadForHttpClientErrorResponsePage) { + EXPECT_CALL(ads_service_mock(), + NotifyTabDidLoad(TabId(), net::HTTP_NOT_FOUND)); + SimulateHttpStatusCodePage(net::HTTP_NOT_FOUND); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidLoadForHttpRedirectionResponsePage) { + EXPECT_CALL(ads_service_mock(), + NotifyTabDidLoad(TabId(), net::HTTP_MOVED_PERMANENTLY)); + SimulateHttpStatusCodePage(net::HTTP_MOVED_PERMANENTLY); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidLoadForHttpSuccessfulResponsePage) { + EXPECT_CALL(ads_service_mock(), NotifyTabDidLoad(TabId(), net::HTTP_OK)); + SimulateHttpStatusCodePage(net::HTTP_OK); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabHtmlContentDidChangeForRewardsUser) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + + base::RunLoop run_loop; + EXPECT_CALL( + ads_service_mock(), + NotifyTabHtmlContentDidChange( + TabId(), RedirectChainExpectation(kMultiPageApplicationWebpage), + kMultiPageApplicationWebpageHtmlContent)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); +} + +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + NotifyTabHtmlContentDidChangeWithEmptyHtmlForNonRewardsUser) { GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, false); - EXPECT_CALL(ads_service(), NotifyTabTextContentDidChange).Times(0); + base::RunLoop run_loop; + EXPECT_CALL( + ads_service_mock(), + NotifyTabHtmlContentDidChange( + TabId(), RedirectChainExpectation(kMultiPageApplicationWebpage), + /*html=*/::testing::IsEmpty())) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotNotifyTabHtmlContentDidChangeIfTabWasRestored) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); + + // Must occur before the browser is closed. + Profile* const profile = GetProfile(); - base::RunLoop html_content_run_loop; - EXPECT_CALL(ads_service(), - NotifyTabHtmlContentDidChange(_, _, /*html=*/IsEmpty())) - .WillOnce(RunClosure(html_content_run_loop.QuitClosure())); + const ScopedKeepAlive scoped_keep_alive(KeepAliveOrigin::SESSION_RESTORE, + KeepAliveRestartOption::DISABLED); + const ScopedProfileKeepAlive scoped_profile_keep_alive( + profile, ProfileKeepAliveOrigin::kSessionRestore); + CloseBrowserSynchronously(browser()); - const GURL url = https_server().GetURL(kUrlDomain, kUrlPath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + // We should not notify about changes to the tab's HTML content, as the + // session will be restored and the tab will reload. + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange).Times(0); + RestoreBrowser(profile); - html_content_run_loop.Run(); + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, - UserHasJoinedBraveRewardsAndOptedInToNotificationAds) { +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabHtmlContentDidChangeForPreviouslyCommittedNavigation) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); + + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange).Times(0); + GoBack(); + GoForward(); + Reload(); + + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); +} + +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabHtmlContentDidChangeForHttpClientErrorResponsePage) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange).Times(0); + SimulateHttpStatusCodePage(net::HTTP_NOT_FOUND); +} + +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabHtmlContentDidChangeForHttpServerErrorResponsePage) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange).Times(0); + SimulateHttpStatusCodePage(net::HTTP_INTERNAL_SERVER_ERROR); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabHtmlContentDidChangeForSameDocumentNavigation) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + + { + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), NotifyTabHtmlContentDidChange) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kSinglePageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); + } + + { + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), + NotifyTabHtmlContentDidChange( + TabId(), ::testing::Contains(FileName("same_document")), + kSinglePageApplicationWebpageHtmlContent)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + SimulateClick(kSinglePageApplicationClickSelectors, + /*has_user_gesture=*/true); + run_loop.Run(); + } +} + +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + NotifyTabTextContentDidChangeForRewardsUserOptedInToNotificationAds) { GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, true); - base::RunLoop text_content_run_loop; - EXPECT_CALL(ads_service(), - NotifyTabTextContentDidChange(_, _, /*text=*/Not(IsEmpty()))) - .WillOnce(RunClosure(text_content_run_loop.QuitClosure())); + base::RunLoop run_loop; + EXPECT_CALL( + ads_service_mock(), + NotifyTabTextContentDidChange( + TabId(), RedirectChainExpectation(kMultiPageApplicationWebpage), + kMultiPageApplicationWebpageTextContent)) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); +} - base::RunLoop html_content_run_loop; - EXPECT_CALL(ads_service(), - NotifyTabHtmlContentDidChange(_, _, /*html=*/Not(IsEmpty()))) - .WillOnce(RunClosure(html_content_run_loop.QuitClosure())); +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForNonRewardsUser) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, false); - const GURL url = https_server().GetURL(kUrlDomain, kUrlPath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); +} - text_content_run_loop.Run(); - html_content_run_loop.Run(); +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForNonRewardsUserAndOptedOutOfNotificationAds) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, false); + GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, false); + + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, - UserHasJoinedBraveRewardsAndOptedOutNotificationAds) { +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForRewardsUserOptedOutOfNotificationAds) { GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, false); - EXPECT_CALL(ads_service(), NotifyTabTextContentDidChange).Times(0); + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeIfTabWasRestored) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, true); + + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); + + // Must occur before the browser is closed. + Profile* const profile = GetProfile(); + + const ScopedKeepAlive scoped_keep_alive(KeepAliveOrigin::SESSION_RESTORE, + KeepAliveRestartOption::DISABLED); + const ScopedProfileKeepAlive scoped_profile_keep_alive( + profile, ProfileKeepAliveOrigin::kSessionRestore); + CloseBrowserSynchronously(browser()); + + // We should not notify about changes to the tab's text content, as the + // session will be restored and the tab will reload. + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + RestoreBrowser(profile); + + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); +} + +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForPreviouslyCommittedNavigation) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, true); - base::RunLoop html_content_run_loop; - EXPECT_CALL(ads_service(), - NotifyTabHtmlContentDidChange(_, _, /*html=*/Not(IsEmpty()))) - .WillOnce(RunClosure(html_content_run_loop.QuitClosure())); + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); - const GURL url = https_server().GetURL(kUrlDomain, kUrlPath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + GoBack(); + GoForward(); + Reload(); - html_content_run_loop.Run(); + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, LoadSinglePageApplication) { +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForHttpClientErrorResponsePage) { GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, true); - base::RunLoop text_content_run_loop; - EXPECT_CALL(ads_service(), - NotifyTabTextContentDidChange(_, _, /*text=*/Not(IsEmpty()))) - .WillOnce(RunClosure(text_content_run_loop.QuitClosure())); + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + SimulateHttpStatusCodePage(net::HTTP_NOT_FOUND); +} - base::RunLoop html_content_run_loop; - EXPECT_CALL(ads_service(), - NotifyTabHtmlContentDidChange(_, _, /*html=*/Not(IsEmpty()))) - .WillOnce(RunClosure(html_content_run_loop.QuitClosure())); +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForHttpServerErrorResponsePage) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, true); - const GURL url = - https_server().GetURL(kUrlDomain, kSinglePageApplicationUrlPath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + SimulateHttpStatusCodePage(net::HTTP_INTERNAL_SERVER_ERROR); +} - text_content_run_loop.Run(); - html_content_run_loop.Run(); +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabTextContentDidChangeForSameDocumentNavigation) { + GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true); + GetPrefs()->SetBoolean(prefs::kOptedInToNotificationAds, true); + + base::RunLoop run_loop; + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange) + .WillOnce(base::test::RunOnceClosure(run_loop.QuitClosure())); + NavigateToURL(kSinglePageApplicationWebpage, /*has_user_gesture=*/true); + run_loop.Run(); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); + + EXPECT_CALL(ads_service_mock(), NotifyTabTextContentDidChange).Times(0); + SimulateClick(kSinglePageApplicationClickSelectors, + /*has_user_gesture=*/true); + + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, NotifyTabDidChange) { - base::RunLoop tab_did_change_run_loop; - EXPECT_CALL( - ads_service(), - NotifyTabDidChange(/*tab_id=*/_, /*redirect_chain=*/_, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page_=*/false, /*is_visible=*/_)) - .WillRepeatedly(RunClosure(tab_did_change_run_loop.QuitClosure())); +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidStartPlayingMediaForAutoplayVideo) { + GetPrefs()->SetBoolean(::prefs::kAutoplayAllowed, true); + + content::WebContents* const web_contents = GetActiveWebContents(); + MediaWaiter waiter(web_contents); - const GURL url = https_server().GetURL(kUrlDomain, kUrlPath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_CALL(ads_service_mock(), NotifyTabDidStartPlayingMedia); + NavigateToURL(kAutoplayVideoWebpage, /*has_user_gesture=*/true); - tab_did_change_run_loop.Run(); + waiter.WaitForMediaStartedPlaying(); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, - NotifyTabDidChangeForServerErrorResponsePage) { - // Mock unexpected NotifyTabDidChange calls to NotifyTabDidChange on browser - // test startup. - EXPECT_CALL(ads_service(), - NotifyTabDidChange(/*tab_id=*/_, /*redirect_chain=*/_, - /*is_new_navigation=*/_, /*is_restoring=*/_, - /*is_error_page_=*/_, /*is_visible=*/_)) - .Times(testing::AnyNumber()); +IN_PROC_BROWSER_TEST_F( + BraveAdsTabHelperTest, + DoNotNotifyTabDidStartPlayingMediaForAutoplayVideoIfDisallowed) { + GetPrefs()->SetBoolean(::prefs::kAutoplayAllowed, false); - base::RunLoop tab_did_change_run_loop; - EXPECT_CALL( - ads_service(), - NotifyTabDidChange(/*tab_id=*/_, /*redirect_chain=*/_, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page_=*/true, /*is_visible=*/_)) - .WillRepeatedly(RunClosure(tab_did_change_run_loop.QuitClosure())); + content::WebContents* const web_contents = GetActiveWebContents(); + MediaWaiter waiter(web_contents); - const GURL url = https_server().GetURL(kUrlDomain, k500ErrorPagePath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + EXPECT_CALL(ads_service_mock(), NotifyTabDidStartPlayingMedia).Times(0); + NavigateToURL(kAutoplayVideoWebpage, /*has_user_gesture=*/true); - tab_did_change_run_loop.Run(); + waiter.WaitForMediaSessionCreated(); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, - NotifyTabDidChangeForClientErrorResponsePage) { - // Mock unexpected NotifyTabDidChange calls to NotifyTabDidChange on browser - // test startup. - EXPECT_CALL(ads_service(), - NotifyTabDidChange(/*tab_id=*/_, /*redirect_chain=*/_, - /*is_new_navigation=*/_, /*is_restoring=*/_, - /*is_error_page_=*/_, /*is_visible=*/_)) - .Times(testing::AnyNumber()); +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + NotifyTabDidStopPlayingMediaForAutoplayVideo) { + GetPrefs()->SetBoolean(::prefs::kAutoplayAllowed, true); - base::RunLoop tab_did_change_run_loop; - EXPECT_CALL( - ads_service(), - NotifyTabDidChange(/*tab_id=*/_, /*redirect_chain=*/_, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page_=*/true, /*is_visible=*/_)) - .WillRepeatedly(RunClosure(tab_did_change_run_loop.QuitClosure())); + content::WebContents* const web_contents = GetActiveWebContents(); + MediaWaiter waiter(web_contents); + + EXPECT_CALL(ads_service_mock(), NotifyTabDidStartPlayingMedia); + NavigateToURL(kAutoplayVideoWebpage, /*has_user_gesture=*/true); - const GURL url = https_server().GetURL(kUrlDomain, k404ErrorPagePath); - ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + waiter.WaitForMediaStartedPlaying(); - tab_did_change_run_loop.Run(); + EXPECT_CALL(ads_service_mock(), NotifyTabDidStopPlayingMedia); + PauseVideoPlayback(kVideoJavascriptDocumentQuerySelectors); } -IN_PROC_BROWSER_TEST_F(AdsTabHelperTest, IncognitoBrowser) { - const GURL url = https_server().GetURL(kUrlDomain, kUrlPath); - Browser* incognito_browser = OpenURLOffTheRecord(browser()->profile(), url); - AdsTabHelper* incognito_ads_tab_helper = - GetActiveAdsTabHelper(incognito_browser); - EXPECT_FALSE(incognito_ads_tab_helper->ads_service()); +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, NotifyTabDidStartPlayingMedia) { + NavigateToURL(kVideoWebpage, /*has_user_gesture=*/true); + + EXPECT_CALL(ads_service_mock(), NotifyTabDidStartPlayingMedia); + StartVideoPlayback(kVideoJavascriptDocumentQuerySelectors); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, NotifyTabDidStopPlayingMedia) { + NavigateToURL(kVideoWebpage, /*has_user_gesture=*/true); + + StartVideoPlayback(kVideoJavascriptDocumentQuerySelectors); + + EXPECT_CALL(ads_service_mock(), NotifyTabDidStopPlayingMedia); + PauseVideoPlayback(kVideoJavascriptDocumentQuerySelectors); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, NotifyDidCloseTab) { + EXPECT_CALL(ads_service_mock(), NotifyDidCloseTab); + CloseActiveWebContents(); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, NotifyUserGestureEventTriggered) { + EXPECT_CALL(ads_service_mock(), NotifyUserGestureEventTriggered) + .Times(::testing::AtLeast(1)); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotNotifyUserGestureEventTriggered) { + EXPECT_CALL(ads_service_mock(), NotifyUserGestureEventTriggered).Times(0); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/false); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotNotifyUserGestureEventTriggeredIfTabWasRestored) { + EXPECT_CALL(ads_service_mock(), NotifyUserGestureEventTriggered) + .Times(::testing::AtLeast(1)); + NavigateToURL(kMultiPageApplicationWebpage, /*has_user_gesture=*/true); + ::testing::Mock::VerifyAndClearExpectations(&ads_service_mock()); + + // Must occur before the browser is closed. + Profile* const profile = GetProfile(); + + const ScopedKeepAlive scoped_keep_alive(KeepAliveOrigin::SESSION_RESTORE, + KeepAliveRestartOption::DISABLED); + const ScopedProfileKeepAlive scoped_profile_keep_alive( + profile, ProfileKeepAliveOrigin::kSessionRestore); + CloseBrowserSynchronously(browser()); + + EXPECT_CALL(ads_service_mock(), NotifyUserGestureEventTriggered).Times(0); + RestoreBrowser(profile); + + EXPECT_TRUE(WaitForActiveWebContentsToLoad()); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + CreativeAdsServiceForRegularBrowser) { + content::WebContents* const web_contents = GetActiveWebContents(); + ASSERT_TRUE(web_contents); + + AdsTabHelper* const ads_tab_helper = + AdsTabHelper::FromWebContents(web_contents); + ASSERT_TRUE(ads_tab_helper); + + EXPECT_TRUE(ads_tab_helper->ads_service()); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotCreativeAdsServiceForIncognitoBrowser) { + const Browser* const browser = CreateIncognitoBrowser(); + + content::WebContents* const web_contents = + browser->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + + AdsTabHelper* const ads_tab_helper = + AdsTabHelper::FromWebContents(web_contents); + ASSERT_TRUE(ads_tab_helper); + + EXPECT_FALSE(ads_tab_helper->ads_service()); +} + +IN_PROC_BROWSER_TEST_F(BraveAdsTabHelperTest, + DoNotCreativeAdsServiceForGuestBrowser) { + const Browser* const browser = CreateGuestBrowser(); + + content::WebContents* const web_contents = + browser->tab_strip_model()->GetActiveWebContents(); + ASSERT_TRUE(web_contents); + + AdsTabHelper* const ads_tab_helper = + AdsTabHelper::FromWebContents(web_contents); + ASSERT_TRUE(ads_tab_helper); + + EXPECT_FALSE(ads_tab_helper->ads_service()); } } // namespace brave_ads diff --git a/browser/brave_ads/test_ads_service_waiter.cc b/browser/brave_ads/test_ads_service_waiter.cc new file mode 100644 index 000000000000..c46325675288 --- /dev/null +++ b/browser/brave_ads/test_ads_service_waiter.cc @@ -0,0 +1,34 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/brave_ads/test_ads_service_waiter.h" + +#include "base/check.h" +#include "brave/components/brave_ads/browser/ads_service.h" + +namespace brave_ads { + +AdsServiceWaiter::AdsServiceWaiter(AdsService* const ads_service) + : ads_service_(ads_service) { + CHECK(ads_service_); + + ads_service_->AddObserver(this); +} + +AdsServiceWaiter::~AdsServiceWaiter() { + ads_service_->RemoveObserver(this); +} + +void AdsServiceWaiter::Wait() { + run_loop_.Run(); +} + +/////////////////////////////////////////////////////////////////////////////// + +void AdsServiceWaiter::OnAdsServiceInitialized() { + run_loop_.Quit(); +} + +} // namespace brave_ads diff --git a/browser/brave_ads/test_ads_service_waiter.h b/browser/brave_ads/test_ads_service_waiter.h new file mode 100644 index 000000000000..6e0fc99ee24e --- /dev/null +++ b/browser/brave_ads/test_ads_service_waiter.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_BRAVE_ADS_TEST_ADS_SERVICE_WAITER_H_ +#define BRAVE_BROWSER_BRAVE_ADS_TEST_ADS_SERVICE_WAITER_H_ + +#include "base/memory/raw_ptr.h" +#include "base/run_loop.h" +#include "brave/components/brave_ads/browser/ads_service_observer.h" + +namespace brave_ads { + +class AdsService; + +// This class waits for the ads service to be initialized. +class AdsServiceWaiter : public AdsServiceObserver { + public: + explicit AdsServiceWaiter(AdsService* const ads_service); + ~AdsServiceWaiter() override; + + AdsServiceWaiter(const AdsServiceWaiter&) = delete; + AdsServiceWaiter& operator=(const AdsServiceWaiter&) = delete; + + void Wait(); + + private: + // AdsServiceObserver: + void OnAdsServiceInitialized() override; + + const raw_ptr ads_service_; // not owned. + + base::RunLoop run_loop_; +}; + +} // namespace brave_ads + +#endif // BRAVE_BROWSER_BRAVE_ADS_TEST_ADS_SERVICE_WAITER_H_ diff --git a/browser/ui/views/brave_ads/notification_ad_popup_widget.cc b/browser/ui/views/brave_ads/notification_ad_popup_widget.cc index c7be3d9143cf..9a09e68598b4 100644 --- a/browser/ui/views/brave_ads/notification_ad_popup_widget.cc +++ b/browser/ui/views/brave_ads/notification_ad_popup_widget.cc @@ -8,7 +8,7 @@ #include #include "brave/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #include "ui/gfx/geometry/rect.h" #include "ui/native_theme/native_theme.h" #include "ui/views/widget/widget_delegate.h" diff --git a/components/brave_ads/browser/BUILD.gn b/components/brave_ads/browser/BUILD.gn index 78beb71b898d..38ac03770d75 100644 --- a/components/brave_ads/browser/BUILD.gn +++ b/components/brave_ads/browser/BUILD.gn @@ -12,6 +12,7 @@ static_library("browser") { "ad_units/notification_ad/custom_notification_ad_feature.h", "ads_service.cc", "ads_service.h", + "ads_service_observer.h", "analytics/p2a/p2a.cc", "analytics/p2a/p2a.h", "analytics/p2a/p2a_constants.h", diff --git a/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_constants.h b/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_constants.h index 6dd427fa9066..f06ea6efd07f 100644 --- a/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_constants.h +++ b/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_constants.h @@ -6,7 +6,7 @@ #ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_AD_UNITS_NOTIFICATION_AD_CUSTOM_NOTIFICATION_AD_CONSTANTS_H_ #define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_AD_UNITS_NOTIFICATION_AD_CUSTOM_NOTIFICATION_AD_CONSTANTS_H_ -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" namespace brave_ads { diff --git a/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h b/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h index 4dff1d60d8ca..db8ba9a1eeeb 100644 --- a/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h +++ b/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h @@ -11,7 +11,7 @@ #include "base/feature_list.h" #include "base/metrics/field_trial_params.h" #include "brave/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_constants.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" namespace brave_ads { diff --git a/components/brave_ads/browser/ads_service.h b/components/brave_ads/browser/ads_service.h index f983cec66715..07084ba881ba 100644 --- a/components/brave_ads/browser/ads_service.h +++ b/components/brave_ads/browser/ads_service.h @@ -14,6 +14,7 @@ #include "base/memory/raw_ptr.h" #include "base/time/time.h" #include "brave/components/brave_ads/browser/ads_service_callback.h" +#include "brave/components/brave_ads/browser/ads_service_observer.h" #include "brave/components/brave_ads/core/mojom/brave_ads.mojom-forward.h" #include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h" #include "brave/components/brave_ads/core/public/ads_callback.h" @@ -67,6 +68,9 @@ class AdsService : public KeyedService { ~AdsService() override; + virtual void AddObserver(AdsServiceObserver* observer) = 0; + virtual void RemoveObserver(AdsServiceObserver* observer) = 0; + // Returns true if a browser upgrade is required to serve ads. virtual bool IsBrowserUpgradeRequiredToServeAds() const = 0; @@ -258,16 +262,18 @@ class AdsService : public KeyedService { // page. The current page is the last one in the list (so even when there's no // redirect, there should be one entry in the list). `is_restoring` should be // set to `true` if the page is restoring otherwise should be set to `false`. - // `is_error_page` should be set to `true` if an error occurred otherwise - // should be set to `false`. `is_visible` should be set to `true` if `tab_id` - // refers to the currently visible tab otherwise should be set to `false`. + // `is_visible` should be set to `true` if `tab_id` refers to the currently + // visible tab otherwise should be set to `false`. virtual void NotifyTabDidChange(int32_t tab_id, const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible) = 0; + // Called when a browser tab has loaded. `http_status_code` should be set to + // the HTTP status code. + virtual void NotifyTabDidLoad(int32_t tab_id, int http_status_code) = 0; + // Called when a browser tab with the specified `tab_id` is closed. virtual void NotifyDidCloseTab(int32_t tab_id) = 0; diff --git a/components/brave_ads/browser/ads_service_impl.cc b/components/brave_ads/browser/ads_service_impl.cc index 9fc1217e3886..5d44688eee3d 100644 --- a/components/brave_ads/browser/ads_service_impl.cc +++ b/components/brave_ads/browser/ads_service_impl.cc @@ -29,6 +29,7 @@ #include "base/timer/timer.h" #include "brave/components/brave_adaptive_captcha/pref_names.h" #include "brave/components/brave_ads/browser/ad_units/notification_ad/custom_notification_ad_feature.h" +#include "brave/components/brave_ads/browser/ads_service_observer.h" #include "brave/components/brave_ads/browser/analytics/p2a/p2a.h" #include "brave/components/brave_ads/browser/analytics/p3a/notification_ad.h" #include "brave/components/brave_ads/browser/bat_ads_service_factory.h" @@ -58,7 +59,7 @@ #include "brave/components/l10n/common/prefs.h" #include "brave/components/ntp_background_images/common/pref_names.h" #include "brave/components/services/bat_ads/public/interfaces/bat_ads.mojom.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_context.h" #include "mojo/public/cpp/bindings/callback_helpers.h" @@ -233,6 +234,18 @@ AdsServiceImpl::AdsServiceImpl( AdsServiceImpl::~AdsServiceImpl() = default; +void AdsServiceImpl::AddObserver(AdsServiceObserver* const observer) { + CHECK(observer); + + observers_.AddObserver(observer); +} + +void AdsServiceImpl::RemoveObserver(AdsServiceObserver* const observer) { + CHECK(observer); + + observers_.RemoveObserver(observer); +} + /////////////////////////////////////////////////////////////////////////////// bool AdsServiceImpl::IsBatAdsServiceBound() const { @@ -491,6 +504,14 @@ void AdsServiceImpl::InitializeBatAdsCallback(const bool success) { if (bat_ads_client_notifier_remote_.is_bound()) { bat_ads_client_notifier_remote_->NotifyDidInitializeAds(); } + + NotifyAdsServiceInitialized(); +} + +void AdsServiceImpl::NotifyAdsServiceInitialized() const { + for (AdsServiceObserver& observer : observers_) { + observer.OnAdsServiceInitialized(); + } } void AdsServiceImpl::ShutdownAndClearData() { @@ -1407,12 +1428,17 @@ void AdsServiceImpl::NotifyTabDidChange(const int32_t tab_id, const std::vector& redirect_chain, const bool is_new_navigation, const bool is_restoring, - const bool is_error_page, const bool is_visible) { if (bat_ads_client_notifier_remote_.is_bound()) { bat_ads_client_notifier_remote_->NotifyTabDidChange( - tab_id, redirect_chain, is_new_navigation, is_restoring, is_error_page, - is_visible); + tab_id, redirect_chain, is_new_navigation, is_restoring, is_visible); + } +} + +void AdsServiceImpl::NotifyTabDidLoad(const int32_t tab_id, + const int http_status_code) { + if (bat_ads_client_notifier_remote_.is_bound()) { + bat_ads_client_notifier_remote_->NotifyTabDidLoad(tab_id, http_status_code); } } diff --git a/components/brave_ads/browser/ads_service_impl.h b/components/brave_ads/browser/ads_service_impl.h index 5622e6f477d5..7b1a7167f8f7 100644 --- a/components/brave_ads/browser/ads_service_impl.h +++ b/components/brave_ads/browser/ads_service_impl.h @@ -57,12 +57,13 @@ class SharedURLLoaderFactory; namespace brave_ads { +class AdsServiceObserver; class AdsTooltipsDelegate; class BatAdsServiceFactory; class Database; class DeviceId; -struct NewTabPageAdInfo; class ResourceComponent; +struct NewTabPageAdInfo; class AdsServiceImpl final : public AdsService, public bat_ads::mojom::BatAdsClient, @@ -93,6 +94,9 @@ class AdsServiceImpl final : public AdsService, ~AdsServiceImpl() override; + void AddObserver(AdsServiceObserver* observer) override; + void RemoveObserver(AdsServiceObserver* observer) override; + private: using SimpleURLLoaderList = std::list>; @@ -134,6 +138,8 @@ class AdsServiceImpl final : public AdsService, brave_rewards::mojom::RewardsWalletPtr mojom_rewards_wallet); void InitializeBatAdsCallback(bool success); + void NotifyAdsServiceInitialized() const; + void ShutdownAndClearData(); void ShutdownAndClearDataCallback(bool success); @@ -292,8 +298,8 @@ class AdsServiceImpl final : public AdsService, const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible) override; + void NotifyTabDidLoad(int32_t tab_id, int http_status_code) override; void NotifyDidCloseTab(int32_t tab_id) override; void NotifyUserGestureEventTriggered(int32_t page_transition_type) override; @@ -470,9 +476,10 @@ class AdsServiceImpl final : public AdsService, brave_rewards::RewardsServiceObserver> rewards_service_observation_{this}; + base::ObserverList observers_; + mojo::Receiver bat_ads_observer_receiver_{ this}; - mojo::Remote bat_ads_service_remote_; mojo::AssociatedReceiver bat_ads_client_associated_receiver_; diff --git a/components/brave_ads/browser/ads_service_mock.h b/components/brave_ads/browser/ads_service_mock.h index 337d5fbd1382..1833074c80a1 100644 --- a/components/brave_ads/browser/ads_service_mock.h +++ b/components/brave_ads/browser/ads_service_mock.h @@ -12,6 +12,7 @@ #include #include "brave/components/brave_ads/browser/ads_service.h" +#include "brave/components/brave_ads/browser/ads_service_observer.h" #include "brave/components/brave_ads/core/mojom/brave_ads.mojom-forward.h" #include "testing/gmock/include/gmock/gmock.h" @@ -29,6 +30,9 @@ class AdsServiceMock : public AdsService { ~AdsServiceMock() override; + MOCK_METHOD(void, AddObserver, (AdsServiceObserver * observer)); + MOCK_METHOD(void, RemoveObserver, (AdsServiceObserver * observer)); + MOCK_METHOD(void, AddBatAdsObserver, (mojo::PendingRemote @@ -130,8 +134,8 @@ class AdsServiceMock : public AdsService { const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible)); + MOCK_METHOD(void, NotifyTabDidLoad, (int32_t tab_id, int http_status_code)); MOCK_METHOD(void, NotifyDidCloseTab, (int32_t tab_id)); MOCK_METHOD(void, NotifyUserGestureEventTriggered, (int32_t tab_id)); MOCK_METHOD(void, NotifyBrowserDidBecomeActive, ()); diff --git a/components/brave_ads/browser/ads_service_observer.h b/components/brave_ads/browser/ads_service_observer.h new file mode 100644 index 000000000000..362b92879adf --- /dev/null +++ b/components/brave_ads/browser/ads_service_observer.h @@ -0,0 +1,21 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_ADS_SERVICE_OBSERVER_H_ +#define BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_ADS_SERVICE_OBSERVER_H_ + +#include "base/observer_list_types.h" + +namespace brave_ads { + +class AdsServiceObserver : public base::CheckedObserver { + public: + // Invoked when the ads service has initialized. + virtual void OnAdsServiceInitialized() {} +}; + +} // namespace brave_ads + +#endif // BRAVE_COMPONENTS_BRAVE_ADS_BROWSER_ADS_SERVICE_OBSERVER_H_ diff --git a/components/brave_ads/core/internal/BUILD.gn b/components/brave_ads/core/internal/BUILD.gn index 518dde890cf7..1146b08d50e1 100644 --- a/components/brave_ads/core/internal/BUILD.gn +++ b/components/brave_ads/core/internal/BUILD.gn @@ -421,6 +421,8 @@ static_library("internal") { "common/logging_util_internal.cc", "common/logging_util_internal.h", "common/net/http/http_status_code.h", + "common/net/http/http_status_code_util.cc", + "common/net/http/http_status_code_util.h", "common/platform/platform_helper.cc", "common/platform/platform_helper.h", "common/platform/platform_helper_types.h", diff --git a/components/brave_ads/core/internal/account/confirmations/reward/README.md b/components/brave_ads/core/internal/account/confirmations/reward/README.md index 6c6cdaf5149f..84ce6e5e82bc 100644 --- a/components/brave_ads/core/internal/account/confirmations/reward/README.md +++ b/components/brave_ads/core/internal/account/confirmations/reward/README.md @@ -10,7 +10,7 @@ Included when redeeming an anonymous confirmation token: | transactionId | {"transactionId":"c14d370c-1178-4c73-8385-1cfa17200646"} | A unique id for the transaction, which is not linkable between confirmation token redemptions. | | type | {"type":"view"} | Action or interaction that occurred within an advertisement, such as a user clicking the ad.

Supported types:

- view
- click
- landed
- conversion
- media_play[^1]
- media_25[^1]
- media_100[^1]
- upvote
- downvote
- flag
- bookmark                             | | confirmation token | {"blindedPaymentTokens": ["Ev5JE4/9TZI/5TqyN9JWfJ1To0HBwQw2rWeAPcdjX3Q="],
"publicKey": "RJ2i/o/pZkrH+i0aGEMY1G9FXtd7Q7gfRi3YdNRnDDk="} | See [security and privacy model for ad confirmations](https://github.com/brave/brave-browser/wiki/Security-and-privacy-model-for-ad-confirmations). | -| user data | {"buildChannel":"nightly",
"catalog":[{"id":"29e5c8bc0ba319069980bb390d8e8f9b58c05a20"}],
"conversion":[{"action":"view"}],
"createdAtTimestamp":"2020-11-18T12:00:00.000Z",
"httpResponseStatus":"errorPage",
"platform":"windows",
"rotatingHash":"I6KM54gXOrWqRHyrD518LmhePLHpIk4KSgCKOl0e3sc=",
"segment":"sports",
"studies":[{"group":"GroupA","name":"BraveAds.FooStudy"},{"group":"GroupB","name":"BraveAds.BarStudy"}],
"versionNumber":"1.2.3.4"} | See [user data](../../user_data/README.md#brave-rewards-users). | +| user data | {"buildChannel":"nightly",
"catalog":[{"id":"29e5c8bc0ba319069980bb390d8e8f9b58c05a20"}],
"conversion":[{"action":"view"}],
"createdAtTimestamp":"2020-11-18T12:00:00.000Z",
"httpResponseStatus":"500",
"platform":"windows",
"rotatingHash":"I6KM54gXOrWqRHyrD518LmhePLHpIk4KSgCKOl0e3sc=",
"segment":"sports",
"studies":[{"group":"GroupA","name":"BraveAds.FooStudy"},{"group":"GroupB","name":"BraveAds.BarStudy"}],
"versionNumber":"1.2.3.4"} | See [user data](../../user_data/README.md#brave-rewards-users). | Please add to it! diff --git a/components/brave_ads/core/internal/account/issuers/url_request/issuers_url_request.cc b/components/brave_ads/core/internal/account/issuers/url_request/issuers_url_request.cc index 5471a2f94814..2b38704bd72b 100644 --- a/components/brave_ads/core/internal/account/issuers/url_request/issuers_url_request.cc +++ b/components/brave_ads/core/internal/account/issuers/url_request/issuers_url_request.cc @@ -16,7 +16,6 @@ #include "brave/components/brave_ads/core/internal/ads_client/ads_client_util.h" #include "brave/components/brave_ads/core/internal/ads_notifier_manager.h" #include "brave/components/brave_ads/core/internal/common/logging_util.h" -#include "brave/components/brave_ads/core/internal/common/net/http/http_status_code.h" #include "brave/components/brave_ads/core/internal/common/time/time_formatting_util.h" #include "brave/components/brave_ads/core/internal/common/url/url_request_string_util.h" #include "brave/components/brave_ads/core/internal/common/url/url_response_string_util.h" @@ -79,7 +78,7 @@ void IssuersUrlRequest::FetchCallback( is_fetching_ = false; - if (mojom_url_response.status_code == net::kHttpUpgradeRequired) { + if (mojom_url_response.status_code == net::HTTP_UPGRADE_REQUIRED) { BLOG(1, "Failed to fetch issuers as a browser upgrade is required"); return AdsNotifierManager::GetInstance() diff --git a/components/brave_ads/core/internal/account/user_data/README.md b/components/brave_ads/core/internal/account/user_data/README.md index 99012ae4d3b2..fa4a02479bec 100644 --- a/components/brave_ads/core/internal/account/user_data/README.md +++ b/components/brave_ads/core/internal/account/user_data/README.md @@ -29,7 +29,7 @@ Included as part of the "confirmation token" payload. See [Brave Rewards user co | conversion.envelope[^2] | {"conversion":[{"envelope":
{"alg":"crypto_box_curve25519xsalsa20poly1305",
"ciphertext":"3f8a8aeb5e1e4b1a8e7b4e3f8a8aeb5e1e4b1a8e7b4e3f8a8aeb5e1e4b1a8e7b",
"epk":"d1f2e3d4c5b6a79887a6b5c4d3e2f1d1f2e3d4c5b6a79887a6",
"nonce":"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"}}]} | Allow advertisers to affirm that conversions are legitimate by wrapping an identifier with an additional layer of encryption to protect its integrity. Also, see https://ads-help.brave.com/campaign-performance/reporting#verifiable-ad-conversions-vac. | | createdAtTimestamp | {"createdAtTimestamp":"2020-11-18T12:00:00.000Z"} | [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) timestamp with fixed values for minutes, seconds, milliseconds and timezone. | | diagnosticId[^1][^2] | {"diagnosticId":"c1298fde-7fdb-401f-a3ce-0b58fe86e6e2"} | Diagnostic id from [brave://rewards-internals](brave://rewards-internals) to assist in troubleshooting issues. This is only included if it has been set manually by the user, for example, at the request of Brave Support in order to troubleshoot a problem. | -| httpResponseStatus[^2] | {"httpResponseStatus":"errorPage"} | Indicates whether landing on a page resulted in an error page. This is only included if an error occurred. | +| httpResponseStatus | {"httpResponseStatus":"404"} | Indicates the landing page's HTTP status code. | | platform | {"platform":"windows"} | Operating system. | | rotatingHash[^2] | {"rotatingHash":"j9D7eKSoPLYNfxkG2Mx+SbgKJ9hcKg1QwDB8B5qxlpk="} | Hashed device identifier that is unique to each ad and rotated several times a day. This is used for rate-limiting purposes. | | segment[^2] | {"segment":"sports"} | Advertising taxonomy for the chosen ad. Also see, https://ads-help.brave.com/campaign-performance/targeting. | diff --git a/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.cc b/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.cc index 00c7666efa23..5b1d539c9141 100644 --- a/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.cc +++ b/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.cc @@ -5,28 +5,28 @@ #include "brave/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.h" +#include "brave/components/brave_ads/core/internal/common/net/http/http_status_code_util.h" #include "brave/components/brave_ads/core/internal/settings/settings.h" -#include "brave/components/brave_ads/core/internal/tabs/tab_info.h" namespace brave_ads { namespace { constexpr char kHttpResponseStatusKey[] = "httpResponseStatus"; -constexpr char kHttpErrorPageResponseStatus[] = "errorPage"; +constexpr char kNonsensicalHttpStatusCode[] = "---"; } // namespace -base::Value::Dict BuildPageLandUserData(const TabInfo& tab) { +base::Value::Dict BuildPageLandUserData(const int http_status_code) { if (!UserHasJoinedBraveRewards()) { return {}; } base::Value::Dict user_data; - if (tab.is_error_page) { - user_data.Set(kHttpResponseStatusKey, kHttpErrorPageResponseStatus); - } + user_data.Set(kHttpResponseStatusKey, + HttpStatusCodeToString(http_status_code) + .value_or(kNonsensicalHttpStatusCode)); return user_data; } diff --git a/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.h b/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.h index ccd4b31f1334..757ef69d3134 100644 --- a/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.h +++ b/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.h @@ -10,9 +10,7 @@ namespace brave_ads { -struct TabInfo; - -base::Value::Dict BuildPageLandUserData(const TabInfo& tab); +base::Value::Dict BuildPageLandUserData(int http_status_code); } // namespace brave_ads diff --git a/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data_unittest.cc b/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data_unittest.cc index 6ba973810ac4..ae73506e3194 100644 --- a/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data_unittest.cc +++ b/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data_unittest.cc @@ -8,8 +8,7 @@ #include "base/test/values_test_util.h" #include "brave/components/brave_ads/core/internal/common/test/test_base.h" #include "brave/components/brave_ads/core/internal/settings/settings_test_util.h" -#include "brave/components/brave_ads/core/internal/tabs/tab_info.h" -#include "url/gurl.h" +#include "net/http/http_status_code.h" // npm run test -- brave_unit_tests --filter=BraveAds* @@ -18,36 +17,109 @@ namespace brave_ads { class BraveAdsPageLandUserDataTest : public test::TestBase {}; TEST_F(BraveAdsPageLandUserDataTest, - BuildPageLandUserDataForHttpResponseStatusErrorPage) { + BuildPageLandUserDataForHttpInformationalResponseStatusCodeClass) { // Act - const base::Value::Dict user_data = BuildPageLandUserData( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/true, - /*is_playing_media=*/false}); + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_SWITCHING_PROTOCOLS); // Assert EXPECT_EQ(base::test::ParseJsonDict( R"( { - "httpResponseStatus": "errorPage" + "httpResponseStatus": "1xx" })"), user_data); } TEST_F(BraveAdsPageLandUserDataTest, - DoNotBuildPageLandUserDataForHttpResponseStatusNonErrorPage) { + BuildPageLandUserDataForHttpSuccessfulResponseStatusCodeClass) { // Act - const base::Value::Dict user_data = BuildPageLandUserData( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}); + const base::Value::Dict user_data = BuildPageLandUserData(net::HTTP_IM_USED); // Assert - EXPECT_THAT(user_data, ::testing::IsEmpty()); + EXPECT_EQ(base::test::ParseJsonDict( + R"( + { + "httpResponseStatus": "2xx" + })"), + user_data); +} + +TEST_F(BraveAdsPageLandUserDataTest, + BuildPageLandUserDataForHttpRedirectionMessageStatusCodeClass) { + // Act + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_MOVED_PERMANENTLY); + + // Assert + EXPECT_EQ(base::test::ParseJsonDict( + R"( + { + "httpResponseStatus": "3xx" + })"), + user_data); +} + +TEST_F(BraveAdsPageLandUserDataTest, + BuildPageLandUserDataForHttpClientErrorResponseStatusCode) { + // Act + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_NOT_FOUND); + + // Assert + EXPECT_EQ(base::test::ParseJsonDict( + R"( + { + "httpResponseStatus": "404" + })"), + user_data); +} + +TEST_F(BraveAdsPageLandUserDataTest, + BuildPageLandUserDataForHttpClientErrorResponseStatusCodeClass) { + // Act + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_UPGRADE_REQUIRED); + + // Assert + EXPECT_EQ(base::test::ParseJsonDict( + R"( + { + "httpResponseStatus": "4xx" + })"), + user_data); +} + +TEST_F( + BraveAdsPageLandUserDataTest, + BuildPageLandUserDataForPrivacyPreservingHttpServerErrorResponseStatusCode) { + // Act + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_INTERNAL_SERVER_ERROR); + + // Assert + EXPECT_EQ(base::test::ParseJsonDict( + R"( + { + "httpResponseStatus": "500" + })"), + user_data); +} + +TEST_F( + BraveAdsPageLandUserDataTest, + BuildPageLandUserDataForPrivacyPreservingHttpServerErrorResponseStatusCodeClass) { + // Act + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_LOOP_DETECTED); + + // Assert + EXPECT_EQ(base::test::ParseJsonDict( + R"( + { + "httpResponseStatus": "5xx" + })"), + user_data); } TEST_F( @@ -57,12 +129,8 @@ TEST_F( test::DisableBraveRewards(); // Act - const base::Value::Dict user_data = BuildPageLandUserData( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/true, - /*is_playing_media=*/false}); + const base::Value::Dict user_data = + BuildPageLandUserData(net::HTTP_NOT_FOUND); // Assert EXPECT_THAT(user_data, ::testing::IsEmpty()); diff --git a/components/brave_ads/core/internal/account/utility/refill_confirmation_tokens/refill_confirmation_tokens.cc b/components/brave_ads/core/internal/account/utility/refill_confirmation_tokens/refill_confirmation_tokens.cc index 806809be7dc8..0439df12937e 100644 --- a/components/brave_ads/core/internal/account/utility/refill_confirmation_tokens/refill_confirmation_tokens.cc +++ b/components/brave_ads/core/internal/account/utility/refill_confirmation_tokens/refill_confirmation_tokens.cc @@ -30,7 +30,6 @@ #include "brave/components/brave_ads/core/internal/ads_notifier_manager.h" #include "brave/components/brave_ads/core/internal/common/challenge_bypass_ristretto/blinded_token_util.h" #include "brave/components/brave_ads/core/internal/common/logging_util.h" -#include "brave/components/brave_ads/core/internal/common/net/http/http_status_code.h" #include "brave/components/brave_ads/core/internal/common/url/url_request_string_util.h" #include "brave/components/brave_ads/core/internal/common/url/url_response_string_util.h" #include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h" @@ -135,7 +134,7 @@ void RefillConfirmationTokens::RequestSignedTokensCallback( base::expected> RefillConfirmationTokens::HandleRequestSignedTokensUrlResponse( const mojom::UrlResponseInfo& mojom_url_response) { - if (mojom_url_response.status_code == net::kHttpUpgradeRequired) { + if (mojom_url_response.status_code == net::HTTP_UPGRADE_REQUIRED) { AdsNotifierManager::GetInstance().NotifyBrowserUpgradeRequiredToServeAds(); return base::unexpected(std::make_tuple( @@ -209,7 +208,7 @@ RefillConfirmationTokens::HandleGetSignedTokensUrlResponse( CHECK(tokens_); CHECK(blinded_tokens_); - if (mojom_url_response.status_code == net::kHttpUpgradeRequired) { + if (mojom_url_response.status_code == net::HTTP_UPGRADE_REQUIRED) { AdsNotifierManager::GetInstance().NotifyBrowserUpgradeRequiredToServeAds(); return base::unexpected(std::make_tuple( diff --git a/components/brave_ads/core/internal/ad_units/ad_handler.cc b/components/brave_ads/core/internal/ad_units/ad_handler.cc index 5e13ac2fe74d..f6d2534e939b 100644 --- a/components/brave_ads/core/internal/ad_units/ad_handler.cc +++ b/components/brave_ads/core/internal/ad_units/ad_handler.cc @@ -11,7 +11,6 @@ #include "brave/components/brave_ads/core/internal/account/user_data/fixed/page_land_user_data.h" #include "brave/components/brave_ads/core/internal/ads_core/ads_core_util.h" #include "brave/components/brave_ads/core/internal/common/logging_util.h" -#include "brave/components/brave_ads/core/internal/tabs/tab_info.h" #include "brave/components/brave_ads/core/internal/user_engagement/conversions/actions/conversion_action_types_util.h" #include "brave/components/brave_ads/core/internal/user_engagement/conversions/conversion/conversion_info.h" #include "brave/components/brave_ads/core/internal/user_engagement/conversions/conversion/conversion_util.h" @@ -143,33 +142,35 @@ void AdHandler::OnMaybeLandOnPage(const AdInfo& ad, BLOG(1, "Maybe land on page for " << ad.target_url << " in " << after); } -void AdHandler::OnDidSuspendPageLand(const TabInfo& tab, +void AdHandler::OnDidSuspendPageLand(const int32_t tab_id, const base::TimeDelta remaining_time) { BLOG(1, "Suspended page landing on tab id " - << tab.id << " with " << remaining_time << " remaining"); + << tab_id << " with " << remaining_time << " remaining"); } -void AdHandler::OnDidResumePageLand(const TabInfo& tab, +void AdHandler::OnDidResumePageLand(const int32_t tab_id, const base::TimeDelta remaining_time) { - BLOG(1, "Resumed page landing on tab id " << tab.id << " and maybe land in " + BLOG(1, "Resumed page landing on tab id " << tab_id << " and maybe land in " << remaining_time); } -void AdHandler::OnDidLandOnPage(const TabInfo& tab, const AdInfo& ad) { +void AdHandler::OnDidLandOnPage(const int32_t tab_id, + const int32_t http_response_code, + const AdInfo& ad) { CHECK(ad.IsValid()); - BLOG(1, "Landed on page for " << ad.target_url << " on tab id " << tab.id); + BLOG(1, "Landed on page for " << ad.target_url << " on tab id " << tab_id); GetAccount().DepositWithUserData(ad.creative_instance_id, ad.segment, ad.type, mojom::ConfirmationType::kLanded, - BuildPageLandUserData(tab)); + BuildPageLandUserData(http_response_code)); } -void AdHandler::OnDidNotLandOnPage(const TabInfo& tab, const AdInfo& ad) { +void AdHandler::OnDidNotLandOnPage(const int32_t tab_id, const AdInfo& ad) { CHECK(ad.IsValid()); BLOG(1, - "Did not land on page for " << ad.target_url << " on tab id " << tab.id); + "Did not land on page for " << ad.target_url << " on tab id " << tab_id); } void AdHandler::OnCanceledPageLand(const int32_t tab_id, const AdInfo& ad) { diff --git a/components/brave_ads/core/internal/ad_units/ad_handler.h b/components/brave_ads/core/internal/ad_units/ad_handler.h index d9c5a8619b6d..e312fa345ad2 100644 --- a/components/brave_ads/core/internal/ad_units/ad_handler.h +++ b/components/brave_ads/core/internal/ad_units/ad_handler.h @@ -34,7 +34,6 @@ namespace brave_ads { class SiteVisit; struct AdInfo; struct ConversionInfo; -struct TabInfo; class AdHandler final : public ConversionsObserver, SiteVisitObserver { public: @@ -85,12 +84,14 @@ class AdHandler final : public ConversionsObserver, SiteVisitObserver { // SiteVisitObserver: void OnMaybeLandOnPage(const AdInfo& ad, base::TimeDelta after) override; - void OnDidSuspendPageLand(const TabInfo& tab, + void OnDidSuspendPageLand(int32_t tab_id, base::TimeDelta remaining_time) override; - void OnDidResumePageLand(const TabInfo& tab, + void OnDidResumePageLand(int32_t tab_id, base::TimeDelta remaining_time) override; - void OnDidLandOnPage(const TabInfo& tab, const AdInfo& ad) override; - void OnDidNotLandOnPage(const TabInfo& tab, const AdInfo& ad) override; + void OnDidLandOnPage(int32_t tab_id, + int32_t http_response_code, + const AdInfo& ad) override; + void OnDidNotLandOnPage(int32_t tab_id, const AdInfo& ad) override; void OnCanceledPageLand(int32_t tab_id, const AdInfo& ad) override; Catalog catalog_; diff --git a/components/brave_ads/core/internal/ad_units/inline_content_ad/inline_content_ad_test.cc b/components/brave_ads/core/internal/ad_units/inline_content_ad/inline_content_ad_test.cc index 0767eb8b0681..009f1d0a41f6 100644 --- a/components/brave_ads/core/internal/ad_units/inline_content_ad/inline_content_ad_test.cc +++ b/components/brave_ads/core/internal/ad_units/inline_content_ad/inline_content_ad_test.cc @@ -35,7 +35,7 @@ class BraveAdsInlineContentAdIntegrationTest : public test::TestBase { NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("brave://newtab")}, /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_visible=*/true); } void SetUpMocks() override { diff --git a/components/brave_ads/core/internal/ads_client/ads_client_notifier.cc b/components/brave_ads/core/internal/ads_client/ads_client_notifier.cc index 2db4f5c2131b..6b3c220acbe3 100644 --- a/components/brave_ads/core/internal/ads_client/ads_client_notifier.cc +++ b/components/brave_ads/core/internal/ads_client/ads_client_notifier.cc @@ -171,18 +171,29 @@ void AdsClientNotifier::NotifyTabDidChange( const std::vector& redirect_chain, const bool is_new_navigation, const bool is_restoring, - const bool is_error_page, const bool is_visible) { if (should_queue_) { - return queue_->Add(base::BindOnce(&AdsClientNotifier::NotifyTabDidChange, - weak_factory_.GetWeakPtr(), tab_id, - redirect_chain, is_new_navigation, - is_restoring, is_error_page, is_visible)); + return queue_->Add(base::BindOnce( + &AdsClientNotifier::NotifyTabDidChange, weak_factory_.GetWeakPtr(), + tab_id, redirect_chain, is_new_navigation, is_restoring, is_visible)); } for (auto& observer : observers_) { observer.OnNotifyTabDidChange(tab_id, redirect_chain, is_new_navigation, - is_restoring, is_error_page, is_visible); + is_restoring, is_visible); + } +} + +void AdsClientNotifier::NotifyTabDidLoad(const int32_t tab_id, + const int http_status_code) { + if (should_queue_) { + return queue_->Add(base::BindOnce(&AdsClientNotifier::NotifyTabDidLoad, + weak_factory_.GetWeakPtr(), tab_id, + http_status_code)); + } + + for (auto& observer : observers_) { + observer.OnNotifyTabDidLoad(tab_id, http_status_code); } } diff --git a/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.cc b/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.cc index a3229b344036..69f62ad50835 100644 --- a/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.cc +++ b/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.cc @@ -91,11 +91,16 @@ void AdsClientNotifierForTesting::NotifyTabDidChange( const std::vector& redirect_chain, const bool is_new_navigation, const bool is_restoring, - const bool is_error_page, const bool is_visible) { - AdsClientNotifier::NotifyTabDidChange(tab_id, redirect_chain, - is_new_navigation, is_restoring, - is_error_page, is_visible); + AdsClientNotifier::NotifyTabDidChange( + tab_id, redirect_chain, is_new_navigation, is_restoring, is_visible); + + RunTaskEnvironmentUntilIdle(); +} + +void AdsClientNotifierForTesting::NotifyTabDidLoad(const int32_t tab_id, + const int http_status_code) { + AdsClientNotifier::NotifyTabDidLoad(tab_id, http_status_code); RunTaskEnvironmentUntilIdle(); } diff --git a/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.h b/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.h index 52d03c0bdebb..0482cdda3671 100644 --- a/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.h +++ b/components/brave_ads/core/internal/ads_client/ads_client_notifier_for_testing.h @@ -58,8 +58,8 @@ class AdsClientNotifierForTesting : public AdsClientNotifier { const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible) override; + void NotifyTabDidLoad(int32_t tab_id, int http_status_code) override; void NotifyDidCloseTab(int32_t tab_id) override; void NotifyUserGestureEventTriggered(int32_t page_transition_type) override; diff --git a/components/brave_ads/core/internal/ads_client/ads_client_notifier_observer_mock.h b/components/brave_ads/core/internal/ads_client/ads_client_notifier_observer_mock.h index 85c54a5eec6b..00c79af1800b 100644 --- a/components/brave_ads/core/internal/ads_client/ads_client_notifier_observer_mock.h +++ b/components/brave_ads/core/internal/ads_client/ads_client_notifier_observer_mock.h @@ -57,7 +57,8 @@ class AdsClientNotifierObserverMock : public AdsClientNotifierObserver { MOCK_METHOD(void, OnNotifyTabDidStopPlayingMedia, (int32_t)); MOCK_METHOD(void, OnNotifyTabDidChange, - (int32_t, const std::vector&, bool, bool, bool, bool)); + (int32_t, const std::vector&, bool, bool, bool)); + MOCK_METHOD(void, OnNotifyTabDidLoad, (int32_t, int)); MOCK_METHOD(void, OnNotifyDidCloseTab, (int32_t)); MOCK_METHOD(void, OnNotifyUserGestureEventTriggered, (int32_t)); diff --git a/components/brave_ads/core/internal/ads_client/ads_client_notifier_unittest.cc b/components/brave_ads/core/internal/ads_client/ads_client_notifier_unittest.cc index 89248e678286..3c9051f17c20 100644 --- a/components/brave_ads/core/internal/ads_client/ads_client_notifier_unittest.cc +++ b/components/brave_ads/core/internal/ads_client/ads_client_notifier_unittest.cc @@ -9,6 +9,7 @@ #include "base/time/time.h" #include "brave/components/brave_ads/core/internal/ads_client/ads_client_notifier_observer_mock.h" +#include "net/http/http_status_code.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -31,9 +32,10 @@ constexpr char kHtml[] = "HTML"; constexpr int32_t kTabId = 1; constexpr bool kIsNewNavigation = true; constexpr bool kIsRestoring = false; -constexpr bool kIsErrorPage = false; constexpr bool kIsVisible = true; +constexpr int kHttpResponseCode = net::HTTP_OK; + constexpr int32_t kPageTransitionType = 2; constexpr base::TimeDelta kIdleTime = base::Minutes(1); @@ -73,7 +75,8 @@ class BraveAdsAdsClientNotifierTest : public ::testing::Test { ads_client_notifier_.NotifyTabDidStopPlayingMedia(kTabId); ads_client_notifier_.NotifyTabDidChange(kTabId, {GURL(kRedirectChainUrl)}, kIsNewNavigation, kIsRestoring, - kIsErrorPage, kIsVisible); + kIsVisible); + ads_client_notifier_.NotifyTabDidLoad(kTabId, kHttpResponseCode); ads_client_notifier_.NotifyDidCloseTab(kTabId); ads_client_notifier_.NotifyUserGestureEventTriggered(kPageTransitionType); @@ -131,7 +134,10 @@ class BraveAdsAdsClientNotifierTest : public ::testing::Test { EXPECT_CALL(ads_client_notifier_observer_mock_, OnNotifyTabDidChange( kTabId, ::testing::ElementsAre(GURL(kRedirectChainUrl)), - kIsNewNavigation, kIsRestoring, kIsErrorPage, kIsVisible)) + kIsNewNavigation, kIsRestoring, kIsVisible)) + .Times(expected_call_count); + EXPECT_CALL(ads_client_notifier_observer_mock_, + OnNotifyTabDidLoad(kTabId, kHttpResponseCode)) .Times(expected_call_count); EXPECT_CALL(ads_client_notifier_observer_mock_, OnNotifyDidCloseTab(kTabId)) .Times(expected_call_count); diff --git a/components/brave_ads/core/internal/catalog/catalog_url_request.cc b/components/brave_ads/core/internal/catalog/catalog_url_request.cc index 8bb265282381..46c5befe76f3 100644 --- a/components/brave_ads/core/internal/catalog/catalog_url_request.cc +++ b/components/brave_ads/core/internal/catalog/catalog_url_request.cc @@ -18,7 +18,6 @@ #include "brave/components/brave_ads/core/internal/catalog/catalog_url_request_json_reader.h" #include "brave/components/brave_ads/core/internal/catalog/catalog_util.h" #include "brave/components/brave_ads/core/internal/common/logging_util.h" -#include "brave/components/brave_ads/core/internal/common/net/http/http_status_code.h" #include "brave/components/brave_ads/core/internal/common/time/time_formatting_util.h" #include "brave/components/brave_ads/core/internal/common/url/url_request_string_util.h" #include "brave/components/brave_ads/core/internal/common/url/url_response_string_util.h" @@ -81,7 +80,7 @@ void CatalogUrlRequest::FetchCallback( is_fetching_ = false; - if (mojom_url_response.status_code == net::kHttpUpgradeRequired) { + if (mojom_url_response.status_code == net::HTTP_UPGRADE_REQUIRED) { BLOG(1, "Failed to request catalog as a browser upgrade is required"); return AdsNotifierManager::GetInstance() .NotifyBrowserUpgradeRequiredToServeAds(); diff --git a/components/brave_ads/core/internal/common/net/http/http_status_code.h b/components/brave_ads/core/internal/common/net/http/http_status_code.h index 404e3eb981e9..d065e894b84e 100644 --- a/components/brave_ads/core/internal/common/net/http/http_status_code.h +++ b/components/brave_ads/core/internal/common/net/http/http_status_code.h @@ -8,8 +8,9 @@ namespace net { +// HTTP status code 418 "I'm a teapot" is an April Fools' joke from the IETF's +// RFC 2324, Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0). constexpr int kHttpImATeapot = 418; -constexpr int kHttpUpgradeRequired = 426; } // namespace net diff --git a/components/brave_ads/core/internal/common/net/http/http_status_code_util.cc b/components/brave_ads/core/internal/common/net/http/http_status_code_util.cc new file mode 100644 index 000000000000..3cad933b202b --- /dev/null +++ b/components/brave_ads/core/internal/common/net/http/http_status_code_util.cc @@ -0,0 +1,58 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_ads/core/internal/common/net/http/http_status_code_util.h" + +#include "base/containers/fixed_flat_set.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" + +namespace brave_ads { + +namespace { + +constexpr auto kAllowedHttpStatusCodes = base::MakeFixedFlatSet({ + 400, // Bad Request. + 401, // Unauthorized. + 403, // Forbidden. + 404, // Not Found. + 407, // Proxy Authentication Required. + 408, // Request Timeout. + 429, // Too Many Requests. + 451, // Unavailable For Legal Reasons. + 500, // Internal Server Error. + 502, // Bad Gateway. + 503, // Service Unavailable. + 504 // Gateway Timeout. +}); + +std::optional HttpStatusCodeClassToString( + const int http_status_code_class) { + if (http_status_code_class < 1 || http_status_code_class > 5) { + // Nonsensical HTTP status code class. + return std::nullopt; + } + + return base::StringPrintf("%dxx", http_status_code_class); +} + +} // namespace + +std::optional HttpStatusCodeToString(const int http_status_code) { + const int http_status_code_class = http_status_code / 100; + + // Check if the HTTP status code is in the allowed list of codes. + if (const auto iter = kAllowedHttpStatusCodes.find(http_status_code); + iter != kAllowedHttpStatusCodes.cend()) { + // If the HTTP status code is allowed, return it as a string. + return base::NumberToString(http_status_code); + } + + // Return a data minimization status code corresponding to the class of the + // original HTTP status code if the original code is not allowed. + return HttpStatusCodeClassToString(http_status_code_class); +} + +} // namespace brave_ads diff --git a/components/brave_ads/core/internal/common/net/http/http_status_code_util.h b/components/brave_ads/core/internal/common/net/http/http_status_code_util.h new file mode 100644 index 000000000000..343e09ed085f --- /dev/null +++ b/components/brave_ads/core/internal/common/net/http/http_status_code_util.h @@ -0,0 +1,18 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_COMMON_NET_HTTP_HTTP_STATUS_CODE_UTIL_H_ +#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_COMMON_NET_HTTP_HTTP_STATUS_CODE_UTIL_H_ + +#include +#include + +namespace brave_ads { + +std::optional HttpStatusCodeToString(int http_status_code); + +} // namespace brave_ads + +#endif // BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_COMMON_NET_HTTP_HTTP_STATUS_CODE_UTIL_H_ diff --git a/components/brave_ads/core/internal/common/net/http/http_status_code_util_unittest.cc b/components/brave_ads/core/internal/common/net/http/http_status_code_util_unittest.cc new file mode 100644 index 000000000000..d03913935915 --- /dev/null +++ b/components/brave_ads/core/internal/common/net/http/http_status_code_util_unittest.cc @@ -0,0 +1,47 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_ads/core/internal/common/net/http/http_status_code_util.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "testing/gtest/include/gtest/gtest.h" + +// npm run test -- brave_unit_tests --filter=BraveAds* + +namespace brave_ads { + +TEST(BraveAdsHttpStatusCodeUtilTest, HttpStatusCodeToString) { + // Act & Assert + for (int i = 0; i <= 1024; ++i) { + const std::optional http_status_code = + HttpStatusCodeToString(i); + if (!http_status_code) { + // Nonsensical HTTP status code. + continue; + } + + // Allowed HTTP status codes, other codes are mapped to their class. + if (i == 400 || // Bad Request. + i == 401 || // Unauthorized. + i == 403 || // Forbidden. + i == 404 || // Not Found. + i == 407 || // Proxy Authentication Required. + i == 408 || // Request Timeout. + i == 429 || // Too Many Requests. + i == 451 || // Unavailable For Legal Reasons. + i == 500 || // Internal Server Error. + i == 502 || // Bad Gateway. + i == 503 || // Service Unavailable. + i == 504) { // Gateway Timeout. + EXPECT_EQ(base::NumberToString(i), http_status_code); + } else { + EXPECT_EQ(base::StringPrintf("%dxx", /*http_status_code_class*/ i / 100), + http_status_code); + } + } +} + +} // namespace brave_ads diff --git a/components/brave_ads/core/internal/common/platform/platform_helper.cc b/components/brave_ads/core/internal/common/platform/platform_helper.cc index 610254fdf639..565133504378 100644 --- a/components/brave_ads/core/internal/common/platform/platform_helper.cc +++ b/components/brave_ads/core/internal/common/platform/platform_helper.cc @@ -6,7 +6,7 @@ #include "brave/components/brave_ads/core/internal/common/platform/platform_helper.h" #include "base/check_is_test.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #if BUILDFLAG(IS_ANDROID) #include "brave/components/brave_ads/core/internal/common/platform/platform_helper_android.h" #elif BUILDFLAG(IS_IOS) diff --git a/components/brave_ads/core/internal/creatives/notification_ads/notification_ad_manager.cc b/components/brave_ads/core/internal/creatives/notification_ads/notification_ad_manager.cc index 6d8e1e1727c7..2403c80c5706 100644 --- a/components/brave_ads/core/internal/creatives/notification_ads/notification_ad_manager.cc +++ b/components/brave_ads/core/internal/creatives/notification_ads/notification_ad_manager.cc @@ -15,7 +15,7 @@ #include "brave/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_value_util.h" #include "brave/components/brave_ads/core/public/ads_client/ads_client.h" #include "brave/components/brave_ads/core/public/prefs/pref_names.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" #if BUILDFLAG(IS_ANDROID) #include "brave/components/brave_ads/core/internal/application_state/browser_util.h" diff --git a/components/brave_ads/core/internal/flags/flag_constants.h b/components/brave_ads/core/internal/flags/flag_constants.h index 79085fd7b591..bb7cd7a9e3ba 100644 --- a/components/brave_ads/core/internal/flags/flag_constants.h +++ b/components/brave_ads/core/internal/flags/flag_constants.h @@ -7,7 +7,7 @@ #define BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_FLAGS_FLAG_CONSTANTS_H_ #include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" namespace brave_ads { diff --git a/components/brave_ads/core/internal/serving/inline_content_ad_serving_unittest.cc b/components/brave_ads/core/internal/serving/inline_content_ad_serving_unittest.cc index a7c89e9c10f7..b0b0cc24d455 100644 --- a/components/brave_ads/core/internal/serving/inline_content_ad_serving_unittest.cc +++ b/components/brave_ads/core/internal/serving/inline_content_ad_serving_unittest.cc @@ -32,7 +32,7 @@ class BraveAdsInlineContentAdServingTest : public test::TestBase { NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("brave://newtab")}, /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_visible=*/true); SubdivisionTargeting subdivision_targeting; AntiTargetingResource anti_targeting_resource; diff --git a/components/brave_ads/core/internal/serving/permission_rules/media_permission_rule_unittest.cc b/components/brave_ads/core/internal/serving/permission_rules/media_permission_rule_unittest.cc index 3e3ea11d6f2b..3139e1d5baa8 100644 --- a/components/brave_ads/core/internal/serving/permission_rules/media_permission_rule_unittest.cc +++ b/components/brave_ads/core/internal/serving/permission_rules/media_permission_rule_unittest.cc @@ -26,8 +26,7 @@ TEST_F(BraveAdsMediaPermissionRuleTest, // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); @@ -42,8 +41,7 @@ TEST_F(BraveAdsMediaPermissionRuleTest, // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); NotifyTabDidStartPlayingMedia(/*tab_id=*/2); @@ -60,8 +58,7 @@ TEST_F(BraveAdsMediaPermissionRuleTest, // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); NotifyTabDidStartPlayingMedia(/*tab_id=*/2); @@ -77,8 +74,7 @@ TEST_F(BraveAdsMediaPermissionRuleTest, // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); @@ -97,8 +93,7 @@ TEST_F( NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); @@ -111,8 +106,7 @@ TEST_F(BraveAdsMediaPermissionRuleTest, // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); NotifyTabDidStartPlayingMedia(/*tab_id=*/2); @@ -126,8 +120,7 @@ TEST_F(BraveAdsMediaPermissionRuleTest, // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); NotifyTabDidStartPlayingMedia(/*tab_id=*/2); diff --git a/components/brave_ads/core/internal/tabs/tab_info.cc b/components/brave_ads/core/internal/tabs/tab_info.cc index bf60a4f93693..9ceaeb548f0a 100644 --- a/components/brave_ads/core/internal/tabs/tab_info.cc +++ b/components/brave_ads/core/internal/tabs/tab_info.cc @@ -14,12 +14,10 @@ TabInfo::TabInfo() = default; TabInfo::TabInfo(const int32_t id, const bool is_visible, std::vector redirect_chain, - const bool is_error_page, const bool is_playing_media) : id(id), is_visible(is_visible), redirect_chain(std::move(redirect_chain)), - is_error_page(is_error_page), is_playing_media(is_playing_media) {} TabInfo::TabInfo(const TabInfo& other) = default; diff --git a/components/brave_ads/core/internal/tabs/tab_info.h b/components/brave_ads/core/internal/tabs/tab_info.h index 280ebd47aa95..461c06cbe8d5 100644 --- a/components/brave_ads/core/internal/tabs/tab_info.h +++ b/components/brave_ads/core/internal/tabs/tab_info.h @@ -18,7 +18,6 @@ struct TabInfo final { TabInfo(int32_t id, bool is_visible, std::vector redirect_chain, - bool is_error_page, bool is_playing_media); TabInfo(const TabInfo&); @@ -34,7 +33,6 @@ struct TabInfo final { int32_t id = 0; bool is_visible = false; std::vector redirect_chain; - bool is_error_page = false; bool is_playing_media = false; }; diff --git a/components/brave_ads/core/internal/tabs/tab_manager.cc b/components/brave_ads/core/internal/tabs/tab_manager.cc index c5769c445136..6b8a683c7846 100644 --- a/components/brave_ads/core/internal/tabs/tab_manager.cc +++ b/components/brave_ads/core/internal/tabs/tab_manager.cc @@ -60,8 +60,11 @@ std::optional TabManager::MaybeGetForId(const int32_t tab_id) const { } bool TabManager::IsPlayingMedia(const int32_t tab_id) const { - const std::optional tab = MaybeGetForId(tab_id); - return tab ? tab->is_playing_media : false; + if (const std::optional tab = MaybeGetForId(tab_id)) { + return tab->is_playing_media; + } + + return false; } /////////////////////////////////////////////////////////////////////////////// @@ -86,6 +89,14 @@ void TabManager::RemoveForId(const int32_t tab_id) { if (tabs_.empty()) { BLOG(2, "There are no tabs"); + + // If there are no tabs left, clear the visible tab id. + visible_tab_id_.reset(); + return; + } + + if (visible_tab_id_ == tab_id) { + // If the removed tab was the visible one, clear the visible tab id. visible_tab_id_.reset(); } } @@ -102,6 +113,13 @@ void TabManager::NotifyTabDidChange(const TabInfo& tab) const { } } +void TabManager::NotifyTabDidLoad(const TabInfo& tab, + const int http_status_code) const { + for (TabManagerObserver& observer : observers_) { + observer.OnTabDidLoad(tab, http_status_code); + } +} + void TabManager::NotifyDidOpenNewTab(const TabInfo& tab) const { for (TabManagerObserver& observer : observers_) { observer.OnDidOpenNewTab(tab); @@ -206,7 +224,6 @@ void TabManager::OnNotifyTabDidChange(const int32_t tab_id, const std::vector& redirect_chain, const bool is_new_navigation, const bool is_restoring, - const bool is_error_page, const bool is_visible) { CHECK(!redirect_chain.empty()); @@ -224,11 +241,15 @@ void TabManager::OnNotifyTabDidChange(const int32_t tab_id, // Update the tab. tab.is_visible = is_visible; tab.redirect_chain = redirect_chain; - tab.is_error_page = is_error_page; if (is_visible) { // Update the visible tab id. visible_tab_id_ = tab_id; + } else { + if (visible_tab_id_ == tab_id) { + // The visible tab id has become occluded. + visible_tab_id_.reset(); + } } if (is_restoring) { @@ -254,6 +275,13 @@ void TabManager::OnNotifyTabDidChange(const int32_t tab_id, } } +void TabManager::OnNotifyTabDidLoad(const int32_t tab_id, + const int http_status_code) { + if (const std::optional tab = MaybeGetForId(tab_id)) { + NotifyTabDidLoad(*tab, http_status_code); + } +} + void TabManager::OnNotifyDidCloseTab(const int32_t tab_id) { BLOG(2, "Tab id " << tab_id << " was closed"); diff --git a/components/brave_ads/core/internal/tabs/tab_manager.h b/components/brave_ads/core/internal/tabs/tab_manager.h index e8cbfb956c6a..b3e618a48e65 100644 --- a/components/brave_ads/core/internal/tabs/tab_manager.h +++ b/components/brave_ads/core/internal/tabs/tab_manager.h @@ -38,6 +38,7 @@ class TabManager final : public AdsClientNotifierObserver { void AddObserver(TabManagerObserver* observer); void RemoveObserver(TabManagerObserver* observer); + bool IsVisible(int32_t tab_id) const { return visible_tab_id_ == tab_id; } std::optional MaybeGetVisible() const; std::optional MaybeGetForId(int32_t tab_id) const; @@ -51,6 +52,7 @@ class TabManager final : public AdsClientNotifierObserver { void NotifyTabDidChangeFocus(int32_t tab_id) const; void NotifyTabDidChange(const TabInfo& tab) const; + void NotifyTabDidLoad(const TabInfo& tab, int http_status_code) const; void NotifyDidOpenNewTab(const TabInfo& tab) const; void NotifyTextContentDidChange(int32_t tab_id, const std::vector& redirect_chain, @@ -75,8 +77,8 @@ class TabManager final : public AdsClientNotifierObserver { const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible) override; + void OnNotifyTabDidLoad(int32_t tab_id, int http_status_code) override; void OnNotifyDidCloseTab(int32_t tab_id) override; base::ObserverList observers_; diff --git a/components/brave_ads/core/internal/tabs/tab_manager_observer.h b/components/brave_ads/core/internal/tabs/tab_manager_observer.h index 44c5bb255ae0..c180166b31db 100644 --- a/components/brave_ads/core/internal/tabs/tab_manager_observer.h +++ b/components/brave_ads/core/internal/tabs/tab_manager_observer.h @@ -25,6 +25,9 @@ class TabManagerObserver : public base::CheckedObserver { // Invoked when the `tab` is updated. virtual void OnTabDidChange(const TabInfo& tab) {} + // Invoked when the `tab` has loaded. + virtual void OnTabDidLoad(const TabInfo& tab, const int http_status_code) {} + // Invoked when a new `tab` is opened. virtual void OnDidOpenNewTab(const TabInfo& tab) {} diff --git a/components/brave_ads/core/internal/tabs/tab_manager_unittest.cc b/components/brave_ads/core/internal/tabs/tab_manager_unittest.cc index 351807539ad6..99d190f1dbe3 100644 --- a/components/brave_ads/core/internal/tabs/tab_manager_unittest.cc +++ b/components/brave_ads/core/internal/tabs/tab_manager_unittest.cc @@ -36,13 +36,11 @@ TEST_F(BraveAdsTabManagerTest, OpenNewTab) { OnDidOpenNewTab(TabInfo{/*id=*/1, /*is_visible=*/true, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, /*is_playing_media=*/false})); EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); } TEST_F(BraveAdsTabManagerTest, DoNotChangeOccludedTabIfMatchingRedirectChain) { @@ -51,15 +49,13 @@ TEST_F(BraveAdsTabManagerTest, DoNotChangeOccludedTabIfMatchingRedirectChain) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChange).Times(0); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); } TEST_F(BraveAdsTabManagerTest, DoNotChangeVisibleTabIfMatchingRedirectChain) { @@ -68,15 +64,13 @@ TEST_F(BraveAdsTabManagerTest, DoNotChangeVisibleTabIfMatchingRedirectChain) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChange).Times(0); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); } TEST_F(BraveAdsTabManagerTest, DoNotNotifyForRestoredTabs) { @@ -86,8 +80,7 @@ TEST_F(BraveAdsTabManagerTest, DoNotNotifyForRestoredTabs) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus).Times(0); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/true, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/true, /*is_visible=*/true); } TEST_F(BraveAdsTabManagerTest, @@ -96,8 +89,7 @@ TEST_F(BraveAdsTabManagerTest, EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChange).Times(0); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/false, /*is_restoring=*/true, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/false, /*is_restoring=*/true, /*is_visible=*/true); } TEST_F(BraveAdsTabManagerTest, ChangeTabFocusToOccluded) { @@ -106,15 +98,13 @@ TEST_F(BraveAdsTabManagerTest, ChangeTabFocusToOccluded) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus(/*tab_id=*/1)); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); } TEST_F(BraveAdsTabManagerTest, ChangeTabFocusToVisible) { @@ -123,15 +113,13 @@ TEST_F(BraveAdsTabManagerTest, ChangeTabFocusToVisible) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus(/*tab_id=*/1)); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); } TEST_F(BraveAdsTabManagerTest, ChangeOccudedTabIfMismatchingRedirectChain) { @@ -140,8 +128,7 @@ TEST_F(BraveAdsTabManagerTest, ChangeOccudedTabIfMismatchingRedirectChain) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, @@ -149,13 +136,12 @@ TEST_F(BraveAdsTabManagerTest, ChangeOccudedTabIfMismatchingRedirectChain) { /*id=*/1, /*is_visible=*/false, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_error_page=*/false, - /*is_playing_media=*/false})); + /*is_playing_media=*/ + false})); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); } TEST_F(BraveAdsTabManagerTest, ChangeVisibleTabIfMismatchingRedirectChain) { @@ -164,8 +150,7 @@ TEST_F(BraveAdsTabManagerTest, ChangeVisibleTabIfMismatchingRedirectChain) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, @@ -173,13 +158,12 @@ TEST_F(BraveAdsTabManagerTest, ChangeVisibleTabIfMismatchingRedirectChain) { /*id=*/1, /*is_visible=*/true, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_error_page=*/false, - /*is_playing_media=*/false})); + /*is_playing_media=*/ + false})); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); } TEST_F(BraveAdsTabManagerTest, CloseTab) { @@ -188,8 +172,7 @@ TEST_F(BraveAdsTabManagerTest, CloseTab) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, OnDidCloseTab(/*tab_id=*/1)); @@ -202,8 +185,7 @@ TEST_F(BraveAdsTabManagerTest, IsPlayingMedia) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); EXPECT_CALL(tab_manager_observer_mock_, OnTabDidStartPlayingMedia(/*tab_id=*/1)); @@ -219,8 +201,7 @@ TEST_F(BraveAdsTabManagerTest, IsNotPlayingMedia) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_FALSE(TabManager::GetInstance().IsPlayingMedia(/*tab_id=*/1)); @@ -232,8 +213,7 @@ TEST_F(BraveAdsTabManagerTest, StartPlayingMedia) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(tab_manager_observer_mock_, @@ -248,7 +228,7 @@ TEST_F(BraveAdsTabManagerTest, DoNotStartPlayingMediaIfAlreadyPlaying) { NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_visible=*/true); EXPECT_CALL(tab_manager_observer_mock_, OnTabDidStartPlayingMedia(/*tab_id=*/1)); NotifyTabDidStartPlayingMedia(/*tab_id=*/1); @@ -263,8 +243,7 @@ TEST_F(BraveAdsTabManagerTest, StopPlayingMedia) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); EXPECT_CALL(tab_manager_observer_mock_, OnTabDidStartPlayingMedia(/*tab_id=*/1)); @@ -282,14 +261,13 @@ TEST_F(BraveAdsTabManagerTest, GetVisibleTab) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert const TabInfo tab{/*id=*/1, /*is_visible=*/true, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}; + /*is_playing_media=*/ + false}; EXPECT_EQ(tab, TabManager::GetInstance().MaybeGetVisible()); } @@ -304,23 +282,21 @@ TEST_F(BraveAdsTabManagerTest, GetTabForId) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); EXPECT_CALL(tab_manager_observer_mock_, OnDidOpenNewTab); EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert const TabInfo tab{ /*id=*/2, /*is_visible=*/true, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}; + /*is_playing_media=*/ + false}; EXPECT_EQ(tab, TabManager::GetInstance().MaybeGetForId(2)); } @@ -330,8 +306,7 @@ TEST_F(BraveAdsTabManagerTest, DoNotGetIfMissingTab) { EXPECT_CALL(tab_manager_observer_mock_, OnTabDidChangeFocus); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_FALSE(TabManager::GetInstance().MaybeGetForId(2)); diff --git a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.cc b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.cc index 395eeb81a158..cec5359e4f6f 100644 --- a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.cc +++ b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.cc @@ -46,11 +46,12 @@ void SiteVisit::RemoveObserver(SiteVisitObserver* const observer) { /////////////////////////////////////////////////////////////////////////////// -bool SiteVisit::IsLandingPage(const int32_t tab_id) const { +bool SiteVisit::IsLandingOnPage(const int32_t tab_id) const { return base::Contains(page_lands_, tab_id); } -void SiteVisit::MaybeLandOnPage(const TabInfo& tab) { +void SiteVisit::MaybeLandOnPage(const TabInfo& tab, + const int http_status_code) { if (!UserHasJoinedBraveRewards()) { return; } @@ -60,8 +61,9 @@ void SiteVisit::MaybeLandOnPage(const TabInfo& tab) { return; } - if (!IsLandingPage(tab.id)) { - MaybeLandOnPageAfter(tab, *last_clicked_ad_, kPageLandAfter.Get()); + if (!IsLandingOnPage(tab.id)) { + MaybeLandOnPageAfter(tab, http_status_code, *last_clicked_ad_, + kPageLandAfter.Get()); } // Reset the last clicked ad to prevent multiple landings on the same ad. @@ -69,9 +71,10 @@ void SiteVisit::MaybeLandOnPage(const TabInfo& tab) { } void SiteVisit::MaybeLandOnPageAfter(const TabInfo& tab, + const int http_status_code, const AdInfo& ad, const base::TimeDelta page_land_after) { - CHECK(!IsLandingPage(tab.id)); + CHECK(!IsLandingOnPage(tab.id)); if (!DomainOrHostExists(tab.redirect_chain, ad.target_url)) { return BLOG(1, "Visited page does not match the ad landing page"); @@ -79,64 +82,79 @@ void SiteVisit::MaybeLandOnPageAfter(const TabInfo& tab, NotifyMaybeLandOnPage(ad, page_land_after); + page_lands_[tab.id].ad = ad; + // Start the timer to check if the user has navigated to the landing page post // ad click. - page_lands_[tab.id].ad = ad; page_lands_[tab.id].timer.Start( FROM_HERE, page_land_after, base::BindRepeating(&SiteVisit::MaybeLandOnPageAfterCallback, - weak_factory_.GetWeakPtr(), tab)); + weak_factory_.GetWeakPtr(), tab.id, + http_status_code)); + + if (!tab.is_visible) { + // Suspend the page landing if the tab is not visible, i.e., the user opened + // the tab in the background. + SuspendPageLand(tab.id); + } } -void SiteVisit::MaybeLandOnPageAfterCallback(const TabInfo& tab) { - CHECK(IsLandingPage(tab.id)); +void SiteVisit::MaybeLandOnPageAfterCallback(const int32_t tab_id, + const int http_status_code) { + CHECK(IsLandingOnPage(tab_id)); + + const AdInfo ad = page_lands_[tab_id].ad; - const AdInfo ad = page_lands_[tab.id].ad; - if (DidLandOnPage(tab.id, ad.target_url)) { - LandedOnPage(tab, ad); + if (DidLandOnPage(tab_id, ad.target_url)) { + LandedOnPage(tab_id, http_status_code, ad); } else { - DidNotLandOnPage(tab, ad); + DidNotLandOnPage(tab_id, ad); } - StopPageLand(tab.id); + StopPageLand(tab_id); } -void SiteVisit::LandedOnPage(const TabInfo& tab, const AdInfo& ad) { - RecordAdEvent(ad, mojom::ConfirmationType::kLanded, - base::BindOnce(&SiteVisit::LandedOnPageCallback, - weak_factory_.GetWeakPtr(), tab, ad)); +void SiteVisit::LandedOnPage(const int32_t tab_id, + const int http_status_code, + const AdInfo& ad) { + RecordAdEvent( + ad, mojom::ConfirmationType::kLanded, + base::BindOnce(&SiteVisit::LandedOnPageCallback, + weak_factory_.GetWeakPtr(), tab_id, http_status_code, ad)); } -void SiteVisit::LandedOnPageCallback(const TabInfo& tab, +void SiteVisit::LandedOnPageCallback(const int32_t tab_id, + const int http_status_code, const AdInfo& ad, const bool success) const { if (!success) { BLOG(0, "Failed to record ad page land event"); - return NotifyDidNotLandOnPage(tab, ad); + return NotifyDidNotLandOnPage(tab_id, ad); } - NotifyDidLandOnPage(tab, ad); + NotifyDidLandOnPage(tab_id, http_status_code, ad); } -void SiteVisit::DidNotLandOnPage(const TabInfo& tab, const AdInfo& ad) const { - NotifyDidNotLandOnPage(tab, ad); +void SiteVisit::DidNotLandOnPage(const int32_t tab_id, const AdInfo& ad) const { + NotifyDidNotLandOnPage(tab_id, ad); } -void SiteVisit::MaybeCancelPageLand(const TabInfo& tab) { - if (!IsLandingPage(tab.id)) { +void SiteVisit::MaybeCancelPageLand(const int32_t tab_id) { + if (!IsLandingOnPage(tab_id)) { return; } - const AdInfo ad = page_lands_[tab.id].ad; - if (!DidLandOnPage(tab.id, ad.target_url)) { + const AdInfo ad = page_lands_[tab_id].ad; + + if (!DidLandOnPage(tab_id, ad.target_url)) { // The user navigated away from the landing page post ad click. - CancelPageLand(tab.id); + CancelPageLand(tab_id); } } void SiteVisit::CancelPageLand(const int32_t tab_id) { - if (!IsLandingPage(tab_id)) { + if (!IsLandingOnPage(tab_id)) { return; } @@ -165,38 +183,32 @@ void SiteVisit::MaybeSuspendOrResumePageLand(const int32_t tab_id) { return; } - if (!IsLandingPage(tab_id)) { + if (!IsLandingOnPage(tab_id)) { return; } - const std::optional& tab = - TabManager::GetInstance().MaybeGetForId(tab_id); - if (!tab) { - return BLOG(0, "Failed to get tab for id: " << tab_id); - } - - const bool should_resume = tab->is_visible && + const bool should_resume = TabManager::GetInstance().IsVisible(tab_id) && BrowserManager::GetInstance().IsActive() && BrowserManager::GetInstance().IsInForeground(); if (should_resume) { - return ResumePageLand(*tab); + return ResumePageLand(tab_id); } - SuspendPageLand(*tab); + SuspendPageLand(tab_id); } base::TimeDelta SiteVisit::CalculateRemainingTimeToLandOnPage( const int32_t tab_id) { - CHECK(IsLandingPage(tab_id)); + CHECK(IsLandingOnPage(tab_id)); const PageLandInfo& page_land = page_lands_[tab_id]; return page_land.timer.desired_run_time() - base::TimeTicks::Now(); } -void SiteVisit::SuspendPageLand(const TabInfo& tab) { - CHECK(IsLandingPage(tab.id)); +void SiteVisit::SuspendPageLand(const int32_t tab_id) { + CHECK(IsLandingOnPage(tab_id)); - PageLandInfo& page_land = page_lands_[tab.id]; + PageLandInfo& page_land = page_lands_[tab_id]; if (!page_land.timer.IsRunning()) { // We have already checked if the user has navigated to the landing page. @@ -205,17 +217,17 @@ void SiteVisit::SuspendPageLand(const TabInfo& tab) { // `CalculateRemainingTimeToLandOnPage` must be called prior to stopping the // timer, else the remaining time will be zero. - page_land.remaining_time = CalculateRemainingTimeToLandOnPage(tab.id); + page_land.remaining_time = CalculateRemainingTimeToLandOnPage(tab_id); page_land.timer.Stop(); - NotifyDidSuspendPageLand(tab, *page_land.remaining_time); + NotifyDidSuspendPageLand(tab_id, *page_land.remaining_time); } -void SiteVisit::ResumePageLand(const TabInfo& tab) { - CHECK(IsLandingPage(tab.id)); +void SiteVisit::ResumePageLand(const int32_t tab_id) { + CHECK(IsLandingOnPage(tab_id)); - PageLandInfo& page_land = page_lands_[tab.id]; + PageLandInfo& page_land = page_lands_[tab_id]; if (page_land.timer.IsRunning() || !page_land.remaining_time) { // This is triggered when a new tab is opened, since the page land has not @@ -227,7 +239,7 @@ void SiteVisit::ResumePageLand(const TabInfo& tab) { page_land.timer.Start(FROM_HERE, *page_land.remaining_time, page_land.timer.user_task()); - NotifyDidResumePageLand(tab, *page_land.remaining_time); + NotifyDidResumePageLand(tab_id, *page_land.remaining_time); page_land.remaining_time.reset(); } @@ -240,32 +252,33 @@ void SiteVisit::NotifyMaybeLandOnPage(const AdInfo& ad, } void SiteVisit::NotifyDidSuspendPageLand( - const TabInfo& tab, + const int32_t tab_id, const base::TimeDelta remaining_time) const { for (SiteVisitObserver& observer : observers_) { - observer.OnDidSuspendPageLand(tab, remaining_time); + observer.OnDidSuspendPageLand(tab_id, remaining_time); } } void SiteVisit::NotifyDidResumePageLand( - const TabInfo& tab, + const int32_t tab_id, const base::TimeDelta remaining_time) const { for (SiteVisitObserver& observer : observers_) { - observer.OnDidResumePageLand(tab, remaining_time); + observer.OnDidResumePageLand(tab_id, remaining_time); } } -void SiteVisit::NotifyDidLandOnPage(const TabInfo& tab, +void SiteVisit::NotifyDidLandOnPage(const int32_t tab_id, + const int http_status_code, const AdInfo& ad) const { for (SiteVisitObserver& observer : observers_) { - observer.OnDidLandOnPage(tab, ad); + observer.OnDidLandOnPage(tab_id, http_status_code, ad); } } -void SiteVisit::NotifyDidNotLandOnPage(const TabInfo& tab, +void SiteVisit::NotifyDidNotLandOnPage(const int32_t tab_id, const AdInfo& ad) const { for (SiteVisitObserver& observer : observers_) { - observer.OnDidNotLandOnPage(tab, ad); + observer.OnDidNotLandOnPage(tab_id, ad); } } @@ -305,12 +318,11 @@ void SiteVisit::OnTabDidChangeFocus(const int32_t tab_id) { } void SiteVisit::OnTabDidChange(const TabInfo& tab) { - MaybeCancelPageLand(tab); - MaybeLandOnPage(tab); + MaybeCancelPageLand(tab.id); } -void SiteVisit::OnDidOpenNewTab(const TabInfo& tab) { - MaybeLandOnPage(tab); +void SiteVisit::OnTabDidLoad(const TabInfo& tab, const int http_status_code) { + MaybeLandOnPage(tab, http_status_code); } void SiteVisit::OnDidCloseTab(const int32_t tab_id) { diff --git a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.h b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.h index 7dcbf15b105d..766a8d1eb2b4 100644 --- a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.h +++ b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit.h @@ -50,35 +50,40 @@ class SiteVisit final : public BrowserManagerObserver, void SetLastClickedAd(const AdInfo& ad) { last_clicked_ad_ = ad; } private: - bool IsLandingPage(int32_t tab_id) const; + // Returns true if the tab is currently landing on a page. + bool IsLandingOnPage(int32_t tab_id) const; - void MaybeLandOnPage(const TabInfo& tab); + void MaybeLandOnPage(const TabInfo& tab, int http_status_code); void MaybeLandOnPageAfter(const TabInfo& tab, + int http_status_code, const AdInfo& ad, base::TimeDelta page_land_after); - void MaybeLandOnPageAfterCallback(const TabInfo& tab); - void LandedOnPage(const TabInfo& tab, const AdInfo& ad); - void LandedOnPageCallback(const TabInfo& tab, + void MaybeLandOnPageAfterCallback(int32_t tab_id, int http_status_code); + void LandedOnPage(int32_t tab_id, int http_status_code, const AdInfo& ad); + void LandedOnPageCallback(int32_t tab_id, + int http_status_code, const AdInfo& ad, bool success) const; - void DidNotLandOnPage(const TabInfo& tab, const AdInfo& ad) const; - void MaybeCancelPageLand(const TabInfo& tab); + void DidNotLandOnPage(int32_t tab_id, const AdInfo& ad) const; + void MaybeCancelPageLand(int32_t tab_id); void CancelPageLand(int32_t tab_id); void StopPageLand(int32_t tab_id); void MaybeSuspendOrResumePageLandForVisibleTab(); void MaybeSuspendOrResumePageLand(int32_t tab_id); base::TimeDelta CalculateRemainingTimeToLandOnPage(int32_t tab_id); - void SuspendPageLand(const TabInfo& tab); - void ResumePageLand(const TabInfo& tab); + void SuspendPageLand(int32_t tab_id); + void ResumePageLand(int32_t tab_id); void NotifyMaybeLandOnPage(const AdInfo& ad, base::TimeDelta after) const; - void NotifyDidSuspendPageLand(const TabInfo& tab, + void NotifyDidSuspendPageLand(int32_t tab_id, base::TimeDelta remaining_time) const; - void NotifyDidResumePageLand(const TabInfo& tab, + void NotifyDidResumePageLand(int32_t tab_id, base::TimeDelta remaining_time) const; - void NotifyDidLandOnPage(const TabInfo& tab, const AdInfo& ad) const; - void NotifyDidNotLandOnPage(const TabInfo& tab, const AdInfo& ad) const; + void NotifyDidLandOnPage(int32_t tab_id, + int http_status_code, + const AdInfo& ad) const; + void NotifyDidNotLandOnPage(int32_t tab_id, const AdInfo& ad) const; void NotifyCanceledPageLand(int32_t tab_id, const AdInfo& ad) const; // BrowserManagerObserver: @@ -90,7 +95,7 @@ class SiteVisit final : public BrowserManagerObserver, // TabManagerObserver: void OnTabDidChangeFocus(int32_t tab_id) override; void OnTabDidChange(const TabInfo& tab) override; - void OnDidOpenNewTab(const TabInfo& tab) override; + void OnTabDidLoad(const TabInfo& tab, int http_status_code) override; void OnDidCloseTab(int32_t tab_id) override; base::ObserverList observers_; diff --git a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer.h b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer.h index bf75be619f98..21272cf8b357 100644 --- a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer.h +++ b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer.h @@ -14,7 +14,6 @@ namespace brave_ads { struct AdInfo; -struct TabInfo; class SiteVisitObserver : public base::CheckedObserver { public: @@ -23,23 +22,25 @@ class SiteVisitObserver : public base::CheckedObserver { virtual void OnMaybeLandOnPage(const AdInfo& ad, const base::TimeDelta after) {} - // Invoked when the given `tab` for a page becomes occluded and will resume + // Invoked when the given `tab_id` for a page becomes occluded and will resume // after `remaining_time` when the tab becomes visible. - virtual void OnDidSuspendPageLand(const TabInfo& tab, + virtual void OnDidSuspendPageLand(int32_t tab_id, const base::TimeDelta remaining_time) {} - // Invoked when the given `tab` for a page becomes visible and may load after - // 'remaining_time'. - virtual void OnDidResumePageLand(const TabInfo& tab, + // Invoked when the given `tab_id` for a page becomes visible and may load + // after 'remaining_time'. + virtual void OnDidResumePageLand(int32_t tab_id, const base::TimeDelta remaining_time) {} // Invoked when a user landed on the landing page associated with the given - // `tab` and `ad`. - virtual void OnDidLandOnPage(const TabInfo& tab, const AdInfo& ad) {} - - // Invoked when the user did not land on the landing page for the given `tab` - // and `ad`. - virtual void OnDidNotLandOnPage(const TabInfo& tab, const AdInfo& ad) {} + // `tab_id`, `http_status_code`, and `ad`. + virtual void OnDidLandOnPage(int32_t tab_id, + int http_status_code, + const AdInfo& ad) {} + + // Invoked when the user did not land on the landing page for the given + // `tab_id` and `ad`. + virtual void OnDidNotLandOnPage(int32_t tab_id, const AdInfo& ad) {} // Invoked when canceling a page land for the given `tab_id` and `ad`. virtual void OnCanceledPageLand(const int32_t tab_id, const AdInfo& ad) {} diff --git a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer_mock.h b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer_mock.h index 678c859f5aed..7dd38e9e6fde 100644 --- a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer_mock.h +++ b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer_mock.h @@ -8,7 +8,6 @@ #include -#include "brave/components/brave_ads/core/internal/tabs/tab_info.h" #include "brave/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer.h" #include "testing/gmock/include/gmock/gmock.h" @@ -32,15 +31,21 @@ class SiteVisitObserverMock : public SiteVisitObserver { MOCK_METHOD(void, OnDidSuspendPageLand, - (const TabInfo& tab, const base::TimeDelta remaining_time)); + (const int32_t tab_id, const base::TimeDelta remaining_time)); MOCK_METHOD(void, OnDidResumePageLand, - (const TabInfo& tab, const base::TimeDelta remaining_time)); + (const int32_t tab_id, const base::TimeDelta remaining_time)); - MOCK_METHOD(void, OnDidLandOnPage, (const TabInfo& tab, const AdInfo& ad)); + MOCK_METHOD(void, + OnDidLandOnPage, + (const int32_t tab_id, + const int http_status_code, + const AdInfo& ad)); - MOCK_METHOD(void, OnDidNotLandOnPage, (const TabInfo& tab, const AdInfo& ad)); + MOCK_METHOD(void, + OnDidNotLandOnPage, + (const int32_t tab_id, const AdInfo& ad)); MOCK_METHOD(void, OnCanceledPageLand, diff --git a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_unittest.cc b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_unittest.cc index fc06764cf6b3..f1cfa1672724 100644 --- a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_unittest.cc +++ b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_unittest.cc @@ -12,9 +12,9 @@ #include "brave/components/brave_ads/core/internal/ad_units/ad_test_util.h" #include "brave/components/brave_ads/core/internal/common/test/test_base.h" #include "brave/components/brave_ads/core/internal/settings/settings_test_util.h" -#include "brave/components/brave_ads/core/internal/tabs/tab_info.h" #include "brave/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer_mock.h" #include "brave/components/brave_ads/core/public/user_engagement/site_visit/site_visit_feature.h" +#include "net/http/http_status_code.h" #include "url/gurl.h" // npm run test -- brave_unit_tests --filter=BraveAds* @@ -52,8 +52,7 @@ TEST_F(BraveAdsSiteVisitTest, DoNotLandOnPageIfTheLastClickedAdIsInvalid) { NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage).Times(0); @@ -70,8 +69,7 @@ TEST_F(BraveAdsSiteVisitTest, NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage).Times(0); @@ -88,25 +86,19 @@ TEST_F(BraveAdsSiteVisitTest, DoNotLandOnPageIfTheSameTabIsAlreadyLanding) { OnMaybeLandOnPage(ad, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com/about")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad)); FastForwardClockBy(kPageLandAfter.Get()); } @@ -126,8 +118,8 @@ TEST_F( OnMaybeLandOnPage(ad_1, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Tab 1 (Occluded/Suspend page landing) @@ -135,16 +127,11 @@ TEST_F( EXPECT_CALL(site_visit_observer_mock_, OnDidSuspendPageLand( - TabInfo{/*id=*/1, - /*is_visible=*/false, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/1, /*remaining_time=*/base::Seconds(3))); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); ASSERT_FALSE(HasPendingTasks()); @@ -157,8 +144,8 @@ TEST_F( OnMaybeLandOnPage(ad_2, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/2, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Tab 2 (Occluded/Suspend page landing) @@ -166,67 +153,42 @@ TEST_F( EXPECT_CALL(site_visit_observer_mock_, OnDidSuspendPageLand( - TabInfo{/*id=*/2, - /*is_visible=*/false, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/2, /*remaining_time=*/base::Seconds(7))); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); ASSERT_FALSE(HasPendingTasks()); // Tab 1 (Visible/Resume page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidResumePageLand( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/1, /*remaining_time=*/base::Seconds(3))); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad_1)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad_1)); FastForwardClockToNextPendingTask(); // Tab 2 (Visible/Resume page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidResumePageLand( - TabInfo{/*id=*/2, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/2, /*remaining_time=*/base::Seconds(7))); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/2, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/2, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad_2)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/2, net::HTTP_OK, ad_2)); FastForwardClockToNextPendingTask(); EXPECT_FALSE(HasPendingTasks()); } @@ -243,8 +205,8 @@ TEST_F( OnMaybeLandOnPage(ad, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Browser (Entered background/Suspend page landing) @@ -252,11 +214,7 @@ TEST_F( EXPECT_CALL(site_visit_observer_mock_, OnDidSuspendPageLand( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/1, /*remaining_time=*/base::Seconds(3))); NotifyBrowserDidEnterBackground(); @@ -265,25 +223,15 @@ TEST_F( // Tab 1 (Visible/Resume page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidResumePageLand( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/1, /*remaining_time=*/base::Seconds(3))); NotifyBrowserDidEnterForeground(); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad)); FastForwardClockToNextPendingTask(); EXPECT_FALSE(HasPendingTasks()); } @@ -300,8 +248,8 @@ TEST_F( OnMaybeLandOnPage(ad, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Browser (Entered background/Suspend page landing) @@ -309,11 +257,7 @@ TEST_F( EXPECT_CALL(site_visit_observer_mock_, OnDidSuspendPageLand( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/1, /*remaining_time=*/base::Seconds(3))); NotifyBrowserDidResignActive(); @@ -322,25 +266,15 @@ TEST_F( // Tab 1 (Visible/Resume page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidResumePageLand( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, + /*tab_id=*/1, /*remaining_time=*/base::Seconds(3))); NotifyBrowserDidBecomeActive(); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad)); FastForwardClockToNextPendingTask(); EXPECT_FALSE(HasPendingTasks()); } @@ -360,8 +294,8 @@ TEST_F(BraveAdsSiteVisitTest, DoNotSuspendOrResumePageLand) { OnMaybeLandOnPage(ad, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Browser (Entered background/Suspend page landing) @@ -379,14 +313,8 @@ TEST_F(BraveAdsSiteVisitTest, DoNotSuspendOrResumePageLand) { ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad)); FastForwardClockToNextPendingTask(); EXPECT_FALSE(HasPendingTasks()); } @@ -405,8 +333,7 @@ TEST_F( EXPECT_CALL(site_visit_observer_mock_, OnMaybeLandOnPage).Times(0); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); EXPECT_EQ(0U, GetPendingTaskCount()); } @@ -421,23 +348,18 @@ TEST_F(BraveAdsSiteVisitTest, OnMaybeLandOnPage(ad_1, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Tab 1 (Occluded/Suspend page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidSuspendPageLand( - TabInfo{/*id=*/1, - /*is_visible=*/false, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - kPageLandAfter.Get())); + /*tab_id=*/1, kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/false, /*is_restoring=*/false, + /*is_visible=*/false); ASSERT_FALSE(HasPendingTasks()); // Tab 2 (Visible/Start page landing) @@ -449,74 +371,46 @@ TEST_F(BraveAdsSiteVisitTest, OnMaybeLandOnPage(ad_2, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/2, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Tab 2 (Occluded/Suspend page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidSuspendPageLand( - TabInfo{/*id=*/2, - /*is_visible=*/false, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - kPageLandAfter.Get())); + /*tab_id=*/2, kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); ASSERT_FALSE(HasPendingTasks()); // Tab 1 (Visible/Resume page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidResumePageLand( - TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - kPageLandAfter.Get())); + /*tab_id=*/1, kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad_1)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad_1)); FastForwardClockToNextPendingTask(); // Tab 2 (Visible/Resume page landing) EXPECT_CALL(site_visit_observer_mock_, OnDidResumePageLand( - TabInfo{/*id=*/2, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - kPageLandAfter.Get())); + /*tab_id=*/2, kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/2, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/2, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/2, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad_2)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/2, net::HTTP_OK, ad_2)); FastForwardClockToNextPendingTask(); EXPECT_FALSE(HasPendingTasks()); @@ -533,19 +427,13 @@ TEST_F(BraveAdsSiteVisitTest, OnMaybeLandOnPage(ad, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/false, - /*is_playing_media=*/false}, - ad)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad)); FastForwardClockBy(kPageLandAfter.Get()); } @@ -561,19 +449,13 @@ TEST_F( OnMaybeLandOnPage(ad, /*after=*/kPageLandAfter.Get())); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/true, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); ASSERT_EQ(1U, GetPendingTaskCount()); // Act & Assert - EXPECT_CALL( - site_visit_observer_mock_, - OnDidLandOnPage(TabInfo{/*id=*/1, - /*is_visible=*/true, - /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_error_page=*/true, - /*is_playing_media=*/false}, - ad)); + EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage( + /*tab_id=*/1, net::HTTP_OK, ad)); FastForwardClockBy(kPageLandAfter.Get()); } @@ -585,13 +467,12 @@ TEST_F(BraveAdsSiteVisitTest, DoNotLandOnPageIfTheTabIsOccluded) { NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com/new_tab")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com/new_tab")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/false); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/false); // Act & Assert EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage).Times(0); @@ -609,8 +490,7 @@ TEST_F( NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_CALL(site_visit_observer_mock_, OnDidLandOnPage).Times(0); @@ -626,16 +506,16 @@ TEST_F(BraveAdsSiteVisitTest, NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); // Act & Assert EXPECT_CALL(site_visit_observer_mock_, OnCanceledPageLand(/*tab_id=*/1, ad)); NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://basicattentiontoken.org")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); } TEST_F(BraveAdsSiteVisitTest, CancelPageLandIfTheTabIsClosed) { @@ -646,8 +526,8 @@ TEST_F(BraveAdsSiteVisitTest, CancelPageLandIfTheTabIsClosed) { NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); + NotifyTabDidLoad(/*tab_id=*/1, net::HTTP_OK); // Act & Assert EXPECT_CALL(site_visit_observer_mock_, OnCanceledPageLand(/*tab_id=*/1, ad)); diff --git a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_util_unittest.cc b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_util_unittest.cc index 68dcba7a2ec8..a2578300223c 100644 --- a/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_util_unittest.cc +++ b/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_util_unittest.cc @@ -17,8 +17,7 @@ TEST_F(BraveAdsSiteVisitUtilTest, DidNotLandOnClosedTab) { // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); NotifyDidCloseTab(/*tab_id=*/1); @@ -30,8 +29,7 @@ TEST_F(BraveAdsSiteVisitUtilTest, DidNotLandOnTabIfMismatchingDomainOrHost) { // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://foo.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_FALSE(DidLandOnPage(/*tab_id=*/1, GURL("https://brave.com"))); @@ -41,8 +39,7 @@ TEST_F(BraveAdsSiteVisitUtilTest, DidLandOnPage) { // Arrange NotifyTabDidChange( /*tab_id=*/1, /*redirect_chain=*/{GURL("https://brave.com")}, - /*is_new_navigation=*/true, /*is_restoring=*/false, - /*is_error_page=*/false, /*is_visible=*/true); + /*is_new_navigation=*/true, /*is_restoring=*/false, /*is_visible=*/true); // Act & Assert EXPECT_TRUE(DidLandOnPage(/*tab_id=*/1, GURL("https://brave.com"))); diff --git a/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_constants.h b/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_constants.h index 0f3b004c5385..d7dbb259cde9 100644 --- a/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_constants.h +++ b/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_constants.h @@ -9,7 +9,7 @@ #include #include "base/time/time.h" -#include "build/build_config.h" // IWYU pragma: keep +#include "build/build_config.h" namespace brave_ads { diff --git a/components/brave_ads/core/public/ads_client/ads_client_notifier.h b/components/brave_ads/core/public/ads_client/ads_client_notifier.h index 4dae70772660..156526fc2864 100644 --- a/components/brave_ads/core/public/ads_client/ads_client_notifier.h +++ b/components/brave_ads/core/public/ads_client/ads_client_notifier.h @@ -102,16 +102,18 @@ class AdsClientNotifier { // page. The current page is the last one in the list (so even when there's no // redirect, there should be one entry in the list). `is_restoring` should be // set to `true` if the page is restoring otherwise should be set to `false`. - // `is_error_page` should be set to `true` if an error occurred otherwise - // should be set to `false`. `is_visible` should be set to `true` if `tab_id` - // refers to the currently visible tab otherwise should be set to `false`. + // `is_visible` should be set to `true` if `tab_id` refers to the currently + // visible tab otherwise should be set to `false`. virtual void NotifyTabDidChange(int32_t tab_id, const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible); + // Invoked when a browser tab has loaded. `http_status_code` should be set to + // the HTTP response code. + virtual void NotifyTabDidLoad(int32_t tab_id, int http_status_code); + // Invoked when a browser tab with the specified `tab_id` is closed. virtual void NotifyDidCloseTab(int32_t tab_id); diff --git a/components/brave_ads/core/public/ads_client/ads_client_notifier_observer.h b/components/brave_ads/core/public/ads_client/ads_client_notifier_observer.h index fa4832ec3cf6..2f7d1cafc6a0 100644 --- a/components/brave_ads/core/public/ads_client/ads_client_notifier_observer.h +++ b/components/brave_ads/core/public/ads_client/ads_client_notifier_observer.h @@ -89,16 +89,18 @@ class AdsClientNotifierObserver : public base::CheckedObserver { // page. The current page is the last one in the list (so even when there's no // redirect, there should be one entry in the list). `is_restoring` should be // set to `true` if the page is restoring otherwise should be set to `false`. - // `is_error_page` should be set to `true` if an error occurred otherwise - // should be set to `false`. `is_visible` should be set to `true` if `tab_id` - // refers to the currently visible tab otherwise should be set to `false`. + // `is_visible` should be set to `true` if `tab_id` refers to the currently + // visible tab otherwise should be set to `false`. virtual void OnNotifyTabDidChange(int32_t tab_id, const std::vector& redirect_chain, const bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible) {} + // Invoked when a browser tab has loaded. `http_status_code` should be set to + // the HTTP response code. + virtual void OnNotifyTabDidLoad(int32_t tab_id, int http_status_code) {} + // Invoked when a browser tab with the specified `tab_id` is closed. virtual void OnNotifyDidCloseTab(int32_t tab_id) {} diff --git a/components/brave_ads/core/test/BUILD.gn b/components/brave_ads/core/test/BUILD.gn index 76fc444dc86a..08827fc57735 100644 --- a/components/brave_ads/core/test/BUILD.gn +++ b/components/brave_ads/core/test/BUILD.gn @@ -250,6 +250,7 @@ source_set("brave_ads_unit_tests") { "//brave/components/brave_ads/core/internal/common/country_code/country_code_unittest.cc", "//brave/components/brave_ads/core/internal/common/crypto/crypto_util_unittest.cc", "//brave/components/brave_ads/core/internal/common/locale/locale_util_unittest.cc", + "//brave/components/brave_ads/core/internal/common/net/http/http_status_code_util_unittest.cc", "//brave/components/brave_ads/core/internal/common/platform/platform_helper_mock.cc", "//brave/components/brave_ads/core/internal/common/platform/platform_helper_mock.h", "//brave/components/brave_ads/core/internal/common/resources/country_components_test_constants.h", diff --git a/components/services/bat_ads/bat_ads_client_notifier_impl.cc b/components/services/bat_ads/bat_ads_client_notifier_impl.cc index ee2411b20530..8077d50af879 100644 --- a/components/services/bat_ads/bat_ads_client_notifier_impl.cc +++ b/components/services/bat_ads/bat_ads_client_notifier_impl.cc @@ -99,11 +99,14 @@ void BatAdsClientNotifierImpl::NotifyTabDidChange( const std::vector& redirect_chain, const bool is_new_navigation, const bool is_restoring, - const bool is_error_page, const bool is_visible) { - ads_client_notifier_.NotifyTabDidChange(tab_id, redirect_chain, - is_new_navigation, is_restoring, - is_error_page, is_visible); + ads_client_notifier_.NotifyTabDidChange( + tab_id, redirect_chain, is_new_navigation, is_restoring, is_visible); +} + +void BatAdsClientNotifierImpl::NotifyTabDidLoad(const int32_t tab_id, + const int http_status_code) { + ads_client_notifier_.NotifyTabDidLoad(tab_id, http_status_code); } void BatAdsClientNotifierImpl::NotifyDidCloseTab(const int32_t tab_id) { diff --git a/components/services/bat_ads/bat_ads_client_notifier_impl.h b/components/services/bat_ads/bat_ads_client_notifier_impl.h index a5c709f437d3..0aff62cfd5a3 100644 --- a/components/services/bat_ads/bat_ads_client_notifier_impl.h +++ b/components/services/bat_ads/bat_ads_client_notifier_impl.h @@ -93,16 +93,18 @@ class BatAdsClientNotifierImpl : public bat_ads::mojom::BatAdsClientNotifier { // page. The current page is the last one in the list (so even when there's no // redirect, there should be one entry in the list). `is_restoring` should be // set to `true` if the page is restoring otherwise should be set to `false`. - // `is_error_page` should be set to `true` if an error occurred otherwise - // should be set to `false`. `is_visible` should be set to `true` if `tab_id` - // refers to the currently visible tab otherwise should be set to `false`. + // `is_visible` should be set to `true` if `tab_id` refers to the currently + // visible tab otherwise should be set to `false`. void NotifyTabDidChange(int32_t tab_id, const std::vector& redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible) override; + // Invoked when a browser tab has loaded. `http_status_code` should be set to + // the HTTP response code. + void NotifyTabDidLoad(int32_t tab_id, int http_status_code) override; + // Invoked when a browser tab with the specified `tab_id` is closed. void NotifyDidCloseTab(int32_t tab_id) override; diff --git a/components/services/bat_ads/public/interfaces/bat_ads.mojom b/components/services/bat_ads/public/interfaces/bat_ads.mojom index 03d21a62e977..a9e223c11412 100644 --- a/components/services/bat_ads/public/interfaces/bat_ads.mojom +++ b/components/services/bat_ads/public/interfaces/bat_ads.mojom @@ -46,8 +46,9 @@ interface BatAdsClientNotifier { array redirect_chain, bool is_new_navigation, bool is_restoring, - bool is_error_page, bool is_visible); + NotifyTabDidLoad(int32 tab_id, + int32 http_status_code); NotifyDidCloseTab(int32 tab_id); NotifyUserGestureEventTriggered(int32 page_transition_type); diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Rewards.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Rewards.swift index e5beda3517c8..1d6dabd26d7e 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Rewards.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+Rewards.swift @@ -162,10 +162,7 @@ extension Tab { adsRewardsLog.warning("No favicon found in \(self) to report to rewards panel") } - if rewardsReportingState.wasRestored - || !rewardsReportingState.isNewNavigation - || rewardsReportingState.isErrorPage - { + if rewardsReportingState.wasRestored { return } @@ -199,8 +196,7 @@ extension Tab { group.notify(queue: .main) { rewards.reportLoadedPage( - redirectChain: redirectChain, - tabId: Int(self.rewardsId), + tab: self, htmlContent: htmlContent, textContent: textContent ) diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift index 317b214a10f3..3553c03d39b3 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/BrowserViewController/BVC+WKNavigationDelegate.swift @@ -686,13 +686,7 @@ extension BrowserViewController: WKNavigationDelegate { { let internalUrl = InternalURL(responseURL) - let kHttpClientErrorResponseCodeClass = 4 - let kHttpServerErrorResponseCodeClass = 5 - - let responseCodeClass = response.statusCode / 100 - tab.rewardsReportingState.isErrorPage = - (responseCodeClass == kHttpClientErrorResponseCodeClass - || responseCodeClass == kHttpServerErrorResponseCodeClass) + tab.rewardsReportingState.httpStatusCode = response.statusCode if !tab.rewardsReportingState.wasRestored { tab.rewardsReportingState.wasRestored = internalUrl?.isSessionRestore == true diff --git a/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift b/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift index a1a189d95efc..12e9c7d5f521 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Browser/Tab.swift @@ -72,9 +72,8 @@ struct RewardsTabChangeReportingState { /// Set to true when the resulting page navigation is not a reload or a /// back/forward type. var isNewNavigation = true - /// Set to true when the resulting page is showing an error interstitial or - /// the resulting response from the web page had a 4xx or 5xx status code. - var isErrorPage = false + /// HTTP status code of the resulting page. + var httpStatusCode = -1 } enum TabSecureContentState: String { diff --git a/ios/brave-ios/Sources/Brave/Frontend/Rewards/BraveRewards.swift b/ios/brave-ios/Sources/Brave/Frontend/Rewards/BraveRewards.swift index 7da7ce7fbf0d..5ddae67b637a 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Rewards/BraveRewards.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Rewards/BraveRewards.swift @@ -244,7 +244,6 @@ public class BraveRewards: PreferencesObserver { redirectChain: tab.redirectChain, isNewNavigation: tab.rewardsReportingState.isNewNavigation, isRestoring: tab.rewardsReportingState.wasRestored, - isErrorPage: tab.rewardsReportingState.isErrorPage, isSelected: isSelected ) } @@ -253,28 +252,45 @@ public class BraveRewards: PreferencesObserver { /// Report that a page has loaded in the current browser tab, and the /// text/HTML content is available for analysis. func reportLoadedPage( - redirectChain: [URL], - tabId: Int, + tab: Tab, htmlContent: String?, textContent: String? ) { - guard let url = redirectChain.last else { + guard let url = tab.redirectChain.last else { + // Don't report update for tabs that haven't finished loading. return } + let tabId = Int(tab.rewardsId) + tabRetrieved(tabId, url: url, html: htmlContent) if ads.isServiceRunning() { - ads.notifyTabHtmlContentDidChange( + ads.notifyTabDidLoad( tabId, - redirectChain: redirectChain, - html: htmlContent ?? "" + httpStatusCode: tab.rewardsReportingState.httpStatusCode ) - if let textContent { - ads.notifyTabTextContentDidChange( + + let kHttpClientErrorResponseStatusCodeClass = 4 + let kHttpServerErrorResponseStatusCodeClass = 5 + let responseStatusCodeClass = tab.rewardsReportingState.httpStatusCode / 100 + + if !tab.rewardsReportingState.wasRestored + && tab.rewardsReportingState.isNewNavigation + && responseStatusCodeClass != kHttpClientErrorResponseStatusCodeClass + && responseStatusCodeClass != kHttpServerErrorResponseStatusCodeClass + { + ads.notifyTabHtmlContentDidChange( tabId, - redirectChain: redirectChain, - text: textContent + redirectChain: tab.redirectChain, + html: htmlContent ?? "" ) + if let textContent { + ads.notifyTabTextContentDidChange( + tabId, + redirectChain: tab.redirectChain, + text: textContent + ) + } } } rewardsAPI?.reportLoadedPage(url: url, tabId: UInt32(tabId)) diff --git a/ios/browser/api/ads/brave_ads.h b/ios/browser/api/ads/brave_ads.h index 1540fd25b8dd..174ea1a75fe9 100644 --- a/ios/browser/api/ads/brave_ads.h +++ b/ios/browser/api/ads/brave_ads.h @@ -158,9 +158,11 @@ OBJC_EXPORT redirectChain:(NSArray*)redirectChain isNewNavigation:(BOOL)isNewNavigation isRestoring:(BOOL)isRestoring - isErrorPage:(BOOL)isErrorPage isSelected:(BOOL)isSelected; +- (void)notifyTabDidLoad:(NSInteger)tabId + httpStatusCode:(NSInteger)httpStatusCode; + - (void)notifyDidCloseTab:(NSInteger)tabId; #pragma mark - diff --git a/ios/browser/api/ads/brave_ads.mm b/ios/browser/api/ads/brave_ads.mm index fca35bcc338a..4f9780fd8169 100644 --- a/ios/browser/api/ads/brave_ads.mm +++ b/ios/browser/api/ads/brave_ads.mm @@ -1738,7 +1738,7 @@ - (void)notifyTabTextContentDidChange:(NSInteger)tabId const std::vector urls = [self GURLsWithNSURLs:redirectChain]; adsClientNotifier->NotifyTabTextContentDidChange( - (int32_t)tabId, urls, base::SysNSStringToUTF8(text)); + static_cast(tabId), urls, base::SysNSStringToUTF8(text)); } - (void)notifyTabHtmlContentDidChange:(NSInteger)tabId @@ -1751,18 +1751,20 @@ - (void)notifyTabHtmlContentDidChange:(NSInteger)tabId const std::vector urls = [self GURLsWithNSURLs:redirectChain]; adsClientNotifier->NotifyTabHtmlContentDidChange( - (int32_t)tabId, urls, base::SysNSStringToUTF8(html)); + static_cast(tabId), urls, base::SysNSStringToUTF8(html)); } - (void)notifyTabDidStartPlayingMedia:(NSInteger)tabId { if (adsClientNotifier != nil) { - adsClientNotifier->NotifyTabDidStartPlayingMedia((int32_t)tabId); + adsClientNotifier->NotifyTabDidStartPlayingMedia( + static_cast(tabId)); } } - (void)notifyTabDidStopPlayingMedia:(NSInteger)tabId { if (adsClientNotifier != nil) { - adsClientNotifier->NotifyTabDidStopPlayingMedia((int32_t)tabId); + adsClientNotifier->NotifyTabDidStopPlayingMedia( + static_cast(tabId)); } } @@ -1770,7 +1772,6 @@ - (void)notifyTabDidChange:(NSInteger)tabId redirectChain:(NSArray*)redirectChain isNewNavigation:(BOOL)isNewNavigation isRestoring:(BOOL)isRestoring - isErrorPage:(BOOL)isErrorPage isSelected:(BOOL)isSelected { if (adsClientNotifier == nil) { return; @@ -1780,13 +1781,24 @@ - (void)notifyTabDidChange:(NSInteger)tabId const bool isVisible = isSelected && [self isBrowserActive]; - adsClientNotifier->NotifyTabDidChange((int32_t)tabId, urls, isNewNavigation, - isRestoring, isErrorPage, isVisible); + adsClientNotifier->NotifyTabDidChange(static_cast(tabId), urls, + isNewNavigation, isRestoring, + isVisible); +} + +- (void)notifyTabDidLoad:(NSInteger)tabId + httpStatusCode:(NSInteger)httpStatusCode { + if (adsClientNotifier == nil) { + return; + } + + adsClientNotifier->NotifyTabDidLoad(static_cast(tabId), + static_cast(httpStatusCode)); } - (void)notifyDidCloseTab:(NSInteger)tabId { if (adsClientNotifier != nil) { - adsClientNotifier->NotifyDidCloseTab((int32_t)tabId); + adsClientNotifier->NotifyDidCloseTab(static_cast(tabId)); } } diff --git a/test/BUILD.gn b/test/BUILD.gn index 2e1726abcb48..c3ea3f2e27db 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -838,6 +838,7 @@ test("brave_browser_tests") { "//brave/browser/autoplay:browser_tests", "//brave/browser/brave_ads", "//brave/browser/brave_ads:browser_tests", + "//brave/browser/brave_ads/tabs:browser_tests", "//brave/browser/brave_news:browser_tests", "//brave/browser/brave_rewards", "//brave/browser/brave_rewards/test:browser_tests", diff --git a/test/data/brave_ads/autoplay_video.html b/test/data/brave_ads/autoplay_video.html new file mode 100644 index 000000000000..12429ed65fa4 --- /dev/null +++ b/test/data/brave_ads/autoplay_video.html @@ -0,0 +1,33 @@ + + + + + Brave stops ads from following you + + + + + +

Yeah, Brave blocks that

+ + + + + diff --git a/test/data/brave_ads/basic_page.html b/test/data/brave_ads/basic_page.html deleted file mode 100644 index a98f23c54734..000000000000 --- a/test/data/brave_ads/basic_page.html +++ /dev/null @@ -1,9 +0,0 @@ - -Basic Page - -

Page header

-

- Page content -

- - diff --git a/test/data/brave_ads/multi_page_application.html b/test/data/brave_ads/multi_page_application.html new file mode 100644 index 000000000000..6bcf38faead6 --- /dev/null +++ b/test/data/brave_ads/multi_page_application.html @@ -0,0 +1,42 @@ + + + + + Adventure Awaits + + + +

Welcome to Your Adventure

+

+ Embark on a journey of learning and discovery. Each step you take brings you closer to mastering new skills and + achieving your goals. +

+ +
+ "The only limit to our realization of tomorrow is our doubts of today." - Franklin D. Roosevelt +
+ + + + + + + + + + + + + + + + + +
TaskStatus
Learn RustCompleted
Contribute to a GitHub repositoryIn Progress
Build a mobile appPending
+ + + diff --git a/test/data/brave_ads/single_page_application.html b/test/data/brave_ads/single_page_application.html index 3bce6f88bfd0..8fe0b73894e6 100644 --- a/test/data/brave_ads/single_page_application.html +++ b/test/data/brave_ads/single_page_application.html @@ -1,48 +1,56 @@ - + + + Single Page Application - -

Header

-
  • Home
  • -
  • Eagle
  • -
  • Vulture
  • + +

    Home

    + + diff --git a/test/data/brave_ads/video.html b/test/data/brave_ads/video.html new file mode 100644 index 000000000000..d99be8d8a8e8 --- /dev/null +++ b/test/data/brave_ads/video.html @@ -0,0 +1,33 @@ + + + + + Brave stops ads from following you + + + + + +

    Yeah, Brave blocks that

    + + + + + diff --git a/test/data/brave_ads/video.mp4 b/test/data/brave_ads/video.mp4 new file mode 100644 index 000000000000..69889b2bf485 Binary files /dev/null and b/test/data/brave_ads/video.mp4 differ