Skip to content

Commit

Permalink
[ads] Add search result ad right button click handling
Browse files Browse the repository at this point in the history
  • Loading branch information
aseren committed Oct 7, 2024
1 parent d4aae8f commit b474682
Show file tree
Hide file tree
Showing 28 changed files with 689 additions and 546 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

#include "brave/browser/brave_ads/creatives/search_result_ad/creative_search_result_ad_tab_helper.h"

#include <string>
#include <utility>

#include "base/check.h"
#include "base/check_is_test.h"
#include "base/functional/bind.h"
Expand All @@ -13,6 +16,8 @@
#include "brave/browser/brave_ads/ads_service_factory.h"
#include "brave/components/brave_ads/browser/ads_service.h"
#include "brave/components/brave_ads/content/browser/creatives/search_result_ad/creative_search_result_ad_handler.h"
#include "brave/components/brave_ads/content/browser/creatives/search_result_ad/creative_search_result_ad_url_placement_id_extractor.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h"
#include "brave/components/brave_ads/core/public/ads_feature.h"
#include "brave/components/brave_rewards/common/pref_names.h"
#include "brave/components/brave_search/common/brave_search_utils.h"
Expand Down Expand Up @@ -97,13 +102,6 @@ bool CreativeSearchResultAdTabHelper::ShouldHandleCreativeAdEvents() const {
return profile->GetPrefs()->GetBoolean(brave_rewards::prefs::kEnabled);
}

void CreativeSearchResultAdTabHelper::MaybeTriggerCreativeAdClickedEvent(
const GURL& url) {
if (creative_search_result_ad_handler_) {
creative_search_result_ad_handler_->MaybeTriggerCreativeAdClickedEvent(url);
}
}

///////////////////////////////////////////////////////////////////////////////

AdsService* CreativeSearchResultAdTabHelper::GetAdsService() const {
Expand Down Expand Up @@ -156,14 +154,16 @@ void CreativeSearchResultAdTabHelper::
}

void CreativeSearchResultAdTabHelper::MaybeHandleCreativeAdViewedEvents(
const std::vector<std::string> placement_ids) {
for (const auto& placement_id : placement_ids) {
MaybeHandleCreativeAdViewedEvent(placement_id);
std::vector<mojom::CreativeSearchResultAdInfoPtr>
creative_search_result_ads) {
for (auto& creative_search_result_ad : creative_search_result_ads) {
MaybeHandleCreativeAdViewedEvent(std::move(creative_search_result_ad));
}
}

void CreativeSearchResultAdTabHelper::MaybeHandleCreativeAdViewedEvent(
const std::string& placement_id) {
mojom::CreativeSearchResultAdInfoPtr creative_search_result_ad) {
const std::string& placement_id = creative_search_result_ad->placement_id;
CHECK(!placement_id.empty());

// It is safe to pass `placement_id` directly to the JavaScript function as it
Expand All @@ -175,12 +175,13 @@ void CreativeSearchResultAdTabHelper::MaybeHandleCreativeAdViewedEvent(
base::UTF8ToUTF16(javascript),
base::BindOnce(&CreativeSearchResultAdTabHelper::
MaybeHandleCreativeAdViewedEventCallback,
weak_factory_.GetWeakPtr(), placement_id),
weak_factory_.GetWeakPtr(),
std::move(creative_search_result_ad)),
ISOLATED_WORLD_ID_BRAVE_INTERNAL);
}

void CreativeSearchResultAdTabHelper::MaybeHandleCreativeAdViewedEventCallback(
const std::string& placement_id,
mojom::CreativeSearchResultAdInfoPtr creative_search_result_ad,
const base::Value value) {
const bool is_visible = value.is_bool() && value.GetBool();
if (!is_visible) {
Expand All @@ -190,7 +191,7 @@ void CreativeSearchResultAdTabHelper::MaybeHandleCreativeAdViewedEventCallback(

if (creative_search_result_ad_handler_) {
creative_search_result_ad_handler_->MaybeTriggerCreativeAdViewedEvent(
placement_id);
std::move(creative_search_result_ad));
}
}

Expand All @@ -202,35 +203,23 @@ void CreativeSearchResultAdTabHelper::MaybeHandleCreativeAdClickedEvent(
return;
}

const auto& initiator_origin = navigation_handle->GetInitiatorOrigin();
if (!navigation_handle->IsInPrimaryMainFrame() ||
!ui::PageTransitionCoreTypeIs(navigation_handle->GetPageTransition(),
ui::PAGE_TRANSITION_LINK) ||
!initiator_origin ||
!brave_search::IsAllowedHost(initiator_origin->GetURL())) {
AdsService* ads_service = GetAdsService();
if (!ads_service) {
return;
}

content::WebContents* web_contents = navigation_handle->GetWebContents();
if (!web_contents) {
return;
}

if (content::WebContents* original_opener_web_contents =
web_contents->GetFirstWebContentsInLiveOriginalOpenerChain()) {
web_contents = original_opener_web_contents;
}
CHECK(!navigation_handle->GetRedirectChain().empty());
const GURL url = navigation_handle->GetRedirectChain().back();

CreativeSearchResultAdTabHelper* const creative_search_result_ad_tab_helper =
CreativeSearchResultAdTabHelper::FromWebContents(web_contents);
if (!creative_search_result_ad_tab_helper) {
const std::optional<std::string> placement_id =
MaybeExtractCreativeAdPlacementIdFromUrl(url);
if (!placement_id || placement_id->empty()) {
// The URL does not contain a placement id.
return;
}

CHECK(!navigation_handle->GetRedirectChain().empty());
const GURL url = navigation_handle->GetRedirectChain().back();

creative_search_result_ad_tab_helper->MaybeTriggerCreativeAdClickedEvent(url);
ads_service->TriggerSearchResultAdClickedEvent(
*placement_id, /*intentional*/ base::DoNothing());
}

void CreativeSearchResultAdTabHelper::DidStartNavigation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@
#define BRAVE_BROWSER_BRAVE_ADS_CREATIVES_SEARCH_RESULT_AD_CREATIVE_SEARCH_RESULT_AD_TAB_HELPER_H_

#include <memory>
#include <string>
#include <vector>

#include "base/memory/weak_ptr.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom-forward.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h"

class GURL;

namespace brave_ads {

class AdsService;
Expand All @@ -39,8 +37,6 @@ class CreativeSearchResultAdTabHelper

bool ShouldHandleCreativeAdEvents() const;

void MaybeTriggerCreativeAdClickedEvent(const GURL& url);

private:
friend class content::WebContentsUserData<CreativeSearchResultAdTabHelper>;

Expand All @@ -52,10 +48,13 @@ class CreativeSearchResultAdTabHelper
void MaybeExtractCreativeAdPlacementIdsFromWebPageAndHandleViewedEvents();

void MaybeHandleCreativeAdViewedEvents(
std::vector<std::string> placement_ids);
void MaybeHandleCreativeAdViewedEvent(const std::string& placement_id);
void MaybeHandleCreativeAdViewedEventCallback(const std::string& placement_id,
const base::Value value);
std::vector<mojom::CreativeSearchResultAdInfoPtr>
creative_search_result_ads);
void MaybeHandleCreativeAdViewedEvent(
mojom::CreativeSearchResultAdInfoPtr creative_search_result_ad);
void MaybeHandleCreativeAdViewedEventCallback(
mojom::CreativeSearchResultAdInfoPtr creative_search_result_ad,
const base::Value value);

void MaybeHandleCreativeAdClickedEvent(
content::NavigationHandle* navigation_handle);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ constexpr char kTargetDomain[] = "example.com";
constexpr char kTargetPath[] = "/simple.html";
constexpr char kSearchResultUrlPath[] =
"/brave_ads/creative_search_result_ad.html";
constexpr char kSearchResultClickedUrlPath[] =
"/a/"
"redirect?click_url=https%3A%2F%2Fexample.com%2Fsimple.html&placement_id="
"824657d0-eaed-4b80-8a42-a18c12f2977d";

// Placement IDs are defined in the `search_result_ad_sample.html` file.
constexpr auto kCreativeAdPlacementIdToIndex =
Expand Down Expand Up @@ -129,6 +133,8 @@ class BraveAdsCreativeSearchResultAdTabHelperTest
return http_response;
}

net::EmbeddedTestServer& https_server() { return https_server_; }

PrefService* GetPrefs() { return browser()->profile()->GetPrefs(); }

AdsServiceMock& ads_service() { return ads_service_mock_; }
Expand All @@ -145,7 +151,8 @@ IN_PROC_BROWSER_TEST_F(BraveAdsCreativeSearchResultAdTabHelperTest,
GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, false);

ScopedTestingAdsServiceSetter scoped_setter(&ads_service());
EXPECT_CALL(ads_service(), TriggerSearchResultAdEvent).Times(0);
EXPECT_CALL(ads_service(), TriggerSearchResultAdViewedImpressionEvent)
.Times(0);

const GURL url = GetURL(kAllowedDomain, kSearchResultUrlPath);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
Expand All @@ -160,7 +167,8 @@ IN_PROC_BROWSER_TEST_F(BraveAdsCreativeSearchResultAdTabHelperTest,

GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true);

EXPECT_CALL(ads_service(), TriggerSearchResultAdEvent).Times(0);
EXPECT_CALL(ads_service(), TriggerSearchResultAdViewedImpressionEvent)
.Times(0);

const GURL url = GetURL(kNotAllowedDomain, kSearchResultUrlPath);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
Expand All @@ -175,7 +183,8 @@ IN_PROC_BROWSER_TEST_F(BraveAdsCreativeSearchResultAdTabHelperTest,

GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true);

EXPECT_CALL(ads_service(), TriggerSearchResultAdEvent).Times(0);
EXPECT_CALL(ads_service(), TriggerSearchResultAdViewedImpressionEvent)
.Times(0);

const GURL url =
GetURL(kAllowedDomain, "/brave_ads/invalid_creative_search_result_ad");
Expand Down Expand Up @@ -255,16 +264,12 @@ class SampleBraveAdsCreativeSearchResultAdTabHelperTest
const GURL& url) {
auto run_loop1 = std::make_unique<base::RunLoop>();
auto run_loop2 = std::make_unique<base::RunLoop>();
EXPECT_CALL(
ads_service(),
TriggerSearchResultAdEvent(
::testing::_, mojom::SearchResultAdEventType::kViewedImpression,
::testing::_))
EXPECT_CALL(ads_service(), TriggerSearchResultAdViewedImpressionEvent(
::testing::_, ::testing::_))
.Times(2)
.WillRepeatedly(
[this, &run_loop1, &run_loop2](
mojom::CreativeSearchResultAdInfoPtr mojom_creative_ad,
const mojom::SearchResultAdEventType mojom_ad_event_type,
TriggerAdEventCallback callback) {
ASSERT_TRUE(mojom_creative_ad);

Expand Down Expand Up @@ -305,22 +310,15 @@ IN_PROC_BROWSER_TEST_F(SampleBraveAdsCreativeSearchResultAdTabHelperTest,
LoadAndCheckSampleSearchResultAdWebPage(GetSearchResultUrl());

base::RunLoop run_loop;
EXPECT_CALL(ads_service(), TriggerSearchResultAdEvent)
.WillOnce([this, &run_loop](
mojom::CreativeSearchResultAdInfoPtr mojom_creative_ad,
const mojom::SearchResultAdEventType mojom_ad_event_type,
TriggerAdEventCallback callback) {
EXPECT_EQ(mojom_ad_event_type,
mojom::SearchResultAdEventType::kClicked);
const auto iter =
kCreativeAdPlacementIdToIndex.find(mojom_creative_ad->placement_id);
EXPECT_CALL(ads_service(), TriggerSearchResultAdClickedEvent)
.WillOnce([&run_loop](const std::string& placement_id,
TriggerAdEventCallback callback) {
const auto iter = kCreativeAdPlacementIdToIndex.find(placement_id);
ASSERT_TRUE(iter != kCreativeAdPlacementIdToIndex.cend());
const size_t ad_index = iter->second;
// We clicked on the first ad in `search_result_ad_sample.html`.
EXPECT_EQ(1u, ad_index);

VerifyCreativeAdMetadataExpectations(
mojom_creative_ad, mojom_creative_ad->placement_id, ad_index);
run_loop.Quit();
});

Expand All @@ -339,22 +337,14 @@ IN_PROC_BROWSER_TEST_F(SampleBraveAdsCreativeSearchResultAdTabHelperTest,
LoadAndCheckSampleSearchResultAdWebPage(GetSearchResultUrl());

base::RunLoop run_loop;
EXPECT_CALL(ads_service(), TriggerSearchResultAdEvent)
.WillOnce([this, &run_loop](
mojom::CreativeSearchResultAdInfoPtr mojom_creative_ad,
const mojom::SearchResultAdEventType mojom_ad_event_type,
TriggerAdEventCallback callback) {
EXPECT_EQ(mojom_ad_event_type,
mojom::SearchResultAdEventType::kClicked);
const auto iter =
kCreativeAdPlacementIdToIndex.find(mojom_creative_ad->placement_id);
EXPECT_CALL(ads_service(), TriggerSearchResultAdClickedEvent)
.WillOnce([&run_loop](const std::string& placement_id,
TriggerAdEventCallback callback) {
const auto iter = kCreativeAdPlacementIdToIndex.find(placement_id);
ASSERT_TRUE(iter != kCreativeAdPlacementIdToIndex.cend());
const size_t ad_index = iter->second;
// We clicked on the second ad in `search_result_ad_sample.html`.
EXPECT_EQ(2u, ad_index);

VerifyCreativeAdMetadataExpectations(
mojom_creative_ad, mojom_creative_ad->placement_id, ad_index);
run_loop.Quit();
});

Expand All @@ -363,4 +353,62 @@ IN_PROC_BROWSER_TEST_F(SampleBraveAdsCreativeSearchResultAdTabHelperTest,
run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(SampleBraveAdsCreativeSearchResultAdTabHelperTest,
SearchResultAdOpenedInNewTabByRightClick) {
ScopedTestingAdsServiceSetter scoped_setter(&ads_service());

GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true);

LoadAndCheckSampleSearchResultAdWebPage(GetSearchResultUrl());

base::RunLoop run_loop;
EXPECT_CALL(ads_service(), TriggerSearchResultAdClickedEvent)
.WillOnce([&run_loop](const std::string& placement_id,
TriggerAdEventCallback callback) {
const auto iter = kCreativeAdPlacementIdToIndex.find(placement_id);
ASSERT_TRUE(iter != kCreativeAdPlacementIdToIndex.cend());
const size_t ad_index = iter->second;
// We clicked on the first ad in `search_result_ad_sample.html`.
EXPECT_EQ(1u, ad_index);
run_loop.Quit();
});

const GURL url =
https_server().GetURL(kAllowedDomain, kSearchResultClickedUrlPath);
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));

run_loop.Run();
}

IN_PROC_BROWSER_TEST_F(SampleBraveAdsCreativeSearchResultAdTabHelperTest,
SearchResultAdOpenedInNewWindow) {
ScopedTestingAdsServiceSetter scoped_setter(&ads_service());

GetPrefs()->SetBoolean(brave_rewards::prefs::kEnabled, true);

LoadAndCheckSampleSearchResultAdWebPage(GetSearchResultUrl());

base::RunLoop run_loop;
EXPECT_CALL(ads_service(), TriggerSearchResultAdClickedEvent)
.WillOnce([&run_loop](const std::string& placement_id,
TriggerAdEventCallback callback) {
const auto iter = kCreativeAdPlacementIdToIndex.find(placement_id);
ASSERT_TRUE(iter != kCreativeAdPlacementIdToIndex.cend());
const size_t ad_index = iter->second;
// We clicked on the first ad in `search_result_ad_sample.html`.
EXPECT_EQ(1u, ad_index);
run_loop.Quit();
});

const GURL url =
https_server().GetURL(kAllowedDomain, kSearchResultClickedUrlPath);
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), url, WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));

run_loop.Run();
}

} // namespace brave_ads
18 changes: 13 additions & 5 deletions components/brave_ads/browser/ads_service.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,22 @@ class AdsService : public KeyedService {
mojom::PromotedContentAdEventType mojom_ad_event_type,
TriggerAdEventCallback callback) = 0;

// Called when a user views or interacts with a search result ad to trigger a
// `mojom_ad_event_type` event for the ad specified in `mojom_creative_ad`.
// Called when a user views a search result ad to trigger a `viewed
// impression` event for the ad specified in `mojom_creative_ad`. The callback
// takes one argument - `bool` is set to `true` if successful otherwise
// `false`. Must be called before the
// `mojom::CreativeSearchResultAdInfo::target_url` landing page is opened.
virtual void TriggerSearchResultAdViewedImpressionEvent(
mojom::CreativeSearchResultAdInfoPtr mojom_creative_ad,
TriggerAdEventCallback callback) = 0;

// Called when a user interacts with a search result ad to trigger a
// `clicked` event for the ad specified in `mojom_creative_ad`.
// The callback takes one argument - `bool` is set to `true` if successful
// otherwise `false`. Must be called before the
// `mojom::CreativeSearchResultAdInfo::target_url` landing page is opened.
virtual void TriggerSearchResultAdEvent(
mojom::CreativeSearchResultAdInfoPtr mojom_creative_ad,
mojom::SearchResultAdEventType mojom_ad_event_type,
virtual void TriggerSearchResultAdClickedEvent(
const std::string& placement_id,
TriggerAdEventCallback callback) = 0;

// Called to purge orphaned served ad events for the specified `mojom_ad_type`
Expand Down
Loading

0 comments on commit b474682

Please sign in to comment.