Merge pull request #13215 from brave/issues/22505

Added viewed confirmation throttle for search result ad.
This commit is contained in:
aseren
2022-05-23 05:03:51 -07:00
committed by GitHub
31 changed files with 1142 additions and 164 deletions
+13 -5
View File
@@ -124,6 +124,9 @@ void AdsTabHelper::DidFinishNavigation(
redirect_chain_ = navigation_handle->GetRedirectChain();
if (!navigation_handle->IsSameDocument()) {
if (search_result_ad_service_) {
search_result_ad_service_->OnDidFinishNavigation(tab_id_);
}
should_process_ = navigation_handle->GetRestoreType() ==
content::RestoreType::kNotRestored;
return;
@@ -136,16 +139,17 @@ void AdsTabHelper::DidFinishNavigation(
}
void AdsTabHelper::DocumentOnLoadCompletedInPrimaryMainFrame() {
content::RenderFrameHost* render_frame_host = web_contents()->GetMainFrame();
if (search_result_ad_service_) {
search_result_ad_service_->MaybeRetrieveSearchResultAd(
render_frame_host, tab_id_, should_process_);
}
if (!should_process_) {
return;
}
content::RenderFrameHost* render_frame_host = web_contents()->GetMainFrame();
RunIsolatedJavaScript(render_frame_host);
if (search_result_ad_service_) {
search_result_ad_service_->MaybeRetrieveSearchResultAd(render_frame_host);
}
}
void AdsTabHelper::DidFinishLoad(content::RenderFrameHost* render_frame_host,
@@ -203,6 +207,10 @@ void AdsTabHelper::OnVisibilityChanged(content::Visibility visibility) {
}
void AdsTabHelper::WebContentsDestroyed() {
if (search_result_ad_service_) {
search_result_ad_service_->OnTabClosed(tab_id_);
}
if (!ads_service_) {
return;
}
+30 -1
View File
@@ -10,15 +10,19 @@
#include <utility>
#include "brave/browser/brave_ads/ads_service_factory.h"
#include "brave/browser/brave_ads/search_result_ad/search_result_ad_service_factory.h"
#include "brave/browser/brave_rewards/rewards_service_factory.h"
#include "brave/browser/extensions/api/brave_action_api.h"
#include "brave/browser/extensions/brave_component_loader.h"
#include "brave/components/brave_ads/browser/ads_service.h"
#include "brave/components/brave_ads/content/browser/search_result_ad/search_result_ad_service.h"
#include "brave/components/brave_rewards/browser/rewards_service.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
@@ -30,12 +34,37 @@ constexpr char kAdsEnableRelativeUrl[] = "request_ads_enabled_panel.html";
} // namespace
BraveAdsHost::BraveAdsHost(Profile* profile) : profile_(profile) {
BraveAdsHost::BraveAdsHost(Profile* profile, content::WebContents* web_contents)
: profile_(profile),
tab_id_(sessions::SessionTabHelper::IdForTab(web_contents)) {
DCHECK(profile_);
}
BraveAdsHost::~BraveAdsHost() {}
void BraveAdsHost::MaybeTriggerAdViewedEvent(
const std::string& creative_instance_id,
MaybeTriggerAdViewedEventCallback callback) {
DCHECK(callback);
DCHECK(!creative_instance_id.empty());
if (!tab_id_.is_valid()) {
std::move(callback).Run(/* event_triggered */ false);
return;
}
SearchResultAdService* search_result_ad_service =
SearchResultAdServiceFactory::GetForProfile(profile_);
if (!search_result_ad_service) {
std::move(callback).Run(/* event_triggered */ false);
return;
}
search_result_ad_service->MaybeTriggerSearchResultAdViewedEvent(
creative_instance_id, tab_id_, std::move(callback));
}
void BraveAdsHost::RequestAdsEnabled(RequestAdsEnabledCallback callback) {
DCHECK(callback);
+12 -3
View File
@@ -6,6 +6,7 @@
#ifndef BRAVE_BROWSER_BRAVE_ADS_BRAVE_ADS_HOST_H_
#define BRAVE_BROWSER_BRAVE_ADS_BRAVE_ADS_HOST_H_
#include <string>
#include <vector>
#include "base/memory/raw_ptr.h"
@@ -13,22 +14,29 @@
#include "brave/components/brave_ads/common/brave_ads_host.mojom.h"
#include "brave/components/brave_rewards/browser/rewards_service.h"
#include "brave/components/brave_rewards/browser/rewards_service_observer.h"
#include "components/sessions/core/session_id.h"
class Profile;
namespace content {
class WebContents;
}
namespace brave_ads {
// The class handles chrome.braveRequestAdsEnabled() js api call for Desktop
// platforms. The js api asks the user for permission to enable ads.
// The class handles ads requests from renderer side for Desktop platforms.
class BraveAdsHost : public brave_ads::mojom::BraveAdsHost,
public brave_rewards::RewardsServiceObserver {
public:
explicit BraveAdsHost(Profile* profile);
BraveAdsHost(Profile* profile, content::WebContents* web_contents);
BraveAdsHost(const BraveAdsHost&) = delete;
BraveAdsHost& operator=(const BraveAdsHost&) = delete;
~BraveAdsHost() override;
// brave_ads::mojom::BraveAdsHost
void MaybeTriggerAdViewedEvent(
const std::string& creative_instance_id,
MaybeTriggerAdViewedEventCallback callback) override;
void RequestAdsEnabled(RequestAdsEnabledCallback callback) override;
// brave_rewards::RewardsServiceObserver
@@ -41,6 +49,7 @@ class BraveAdsHost : public brave_ads::mojom::BraveAdsHost,
void RunCallbacksAndReset(bool result);
raw_ptr<Profile> profile_ = nullptr;
SessionID tab_id_;
std::vector<RequestAdsEnabledCallback> callbacks_;
base::ScopedObservation<brave_rewards::RewardsService,
brave_rewards::RewardsServiceObserver>
+31 -1
View File
@@ -10,17 +10,24 @@
#include "base/check.h"
#include "brave/browser/brave_ads/ads_service_factory.h"
#include "brave/browser/brave_ads/android/jni_headers/BraveAdsHostAndroid_jni.h"
#include "brave/browser/brave_ads/search_result_ad/search_result_ad_service_factory.h"
#include "brave/browser/brave_rewards/rewards_service_factory.h"
#include "brave/components/brave_ads/browser/ads_service.h"
#include "brave/components/brave_ads/content/browser/search_result_ad/search_result_ad_service.h"
#include "brave/components/brave_rewards/browser/rewards_service.h"
#include "chrome/browser/profiles/profile.h"
#include "components/sessions/content/session_tab_helper.h"
#include "content/public/browser/web_contents.h"
using base::android::AttachCurrentThread;
using base::android::JavaParamRef;
namespace brave_ads {
BraveAdsHostAndroid::BraveAdsHostAndroid(Profile* profile) : profile_(profile) {
BraveAdsHostAndroid::BraveAdsHostAndroid(Profile* profile,
content::WebContents* web_contents)
: profile_(profile),
tab_id_(sessions::SessionTabHelper::IdForTab(web_contents)) {
DCHECK(profile_);
java_object_.Reset(Java_BraveAdsHostAndroid_create(
@@ -31,6 +38,29 @@ BraveAdsHostAndroid::~BraveAdsHostAndroid() {
Java_BraveAdsHostAndroid_destroy(AttachCurrentThread(), java_object_);
}
void BraveAdsHostAndroid::MaybeTriggerAdViewedEvent(
const std::string& creative_instance_id,
MaybeTriggerAdViewedEventCallback callback) {
DCHECK(callback);
DCHECK(!creative_instance_id.empty());
if (!tab_id_.is_valid()) {
std::move(callback).Run(/* event_triggered */ false);
return;
}
SearchResultAdService* search_result_ad_service =
SearchResultAdServiceFactory::GetForProfile(profile_);
if (!search_result_ad_service) {
std::move(callback).Run(/* event_triggered */ false);
return;
}
search_result_ad_service->MaybeTriggerSearchResultAdViewedEvent(
creative_instance_id, tab_id_, std::move(callback));
}
void BraveAdsHostAndroid::RequestAdsEnabled(
RequestAdsEnabledCallback callback) {
const AdsService* ads_service = AdsServiceFactory::GetForProfile(profile_);
+11 -4
View File
@@ -6,6 +6,7 @@
#ifndef BRAVE_BROWSER_BRAVE_ADS_BRAVE_ADS_HOST_ANDROID_H_
#define BRAVE_BROWSER_BRAVE_ADS_BRAVE_ADS_HOST_ANDROID_H_
#include <string>
#include <vector>
#include "base/android/jni_android.h"
@@ -14,24 +15,29 @@
#include "brave/components/brave_ads/common/brave_ads_host.mojom.h"
#include "brave/components/brave_rewards/browser/rewards_service.h"
#include "brave/components/brave_rewards/browser/rewards_service_observer.h"
#include "components/sessions/core/session_id.h"
class Profile;
class Profile;
namespace content {
class WebContents;
}
namespace brave_ads {
// The class handles chrome.braveRequestAdsEnabled() js api call for Android
// platform. The js api asks the user for permission to enable ads.
// The class handles ads requests from renderer side for Android platform.
class BraveAdsHostAndroid : public brave_ads::mojom::BraveAdsHost,
public brave_rewards::RewardsServiceObserver {
public:
explicit BraveAdsHostAndroid(Profile* profile);
BraveAdsHostAndroid(Profile* profile, content::WebContents* web_contents);
BraveAdsHostAndroid(const BraveAdsHostAndroid&) = delete;
BraveAdsHostAndroid& operator=(const BraveAdsHostAndroid&) = delete;
~BraveAdsHostAndroid() override;
// brave_ads::mojom::BraveAdsHost
void MaybeTriggerAdViewedEvent(
const std::string& creative_instance_id,
MaybeTriggerAdViewedEventCallback callback) override;
void RequestAdsEnabled(RequestAdsEnabledCallback callback) override;
// brave_rewards::RewardsServiceObserver
@@ -47,6 +53,7 @@ class BraveAdsHostAndroid : public brave_ads::mojom::BraveAdsHost,
void RunCallbacksAndReset(bool ads_enabled);
raw_ptr<Profile> profile_ = nullptr;
SessionID tab_id_;
base::android::ScopedJavaGlobalRef<jobject> java_object_;
std::vector<RequestAdsEnabledCallback> callbacks_;
base::ScopedObservation<brave_rewards::RewardsService,
@@ -13,6 +13,7 @@
#include "brave/components/brave_ads/browser/ads_service.h"
#include "brave/components/brave_ads/browser/mock_ads_service.h"
#include "brave/components/brave_ads/common/features.h"
#include "brave/components/brave_ads/common/search_result_ad_util.h"
#include "brave/components/brave_ads/content/browser/search_result_ad/search_result_ad_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
@@ -20,6 +21,7 @@
#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 "content/public/test/url_loader_interceptor.h"
#include "net/dns/mock_host_resolver.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -98,6 +100,49 @@ class ScopedTestingAdsServiceSetter {
raw_ptr<brave_ads::AdsService> previous_ads_service_ = nullptr;
};
class SentViewedEventsWaiter final {
public:
explicit SentViewedEventsWaiter(
std::vector<std::string> creative_instance_ids) {
sent_viewed_creative_instance_ids_ = std::move(creative_instance_ids);
url_loader_interceptor_ =
std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
&SentViewedEventsWaiter::URLLoaderInterceptorCallback,
base::Unretained(this)));
}
~SentViewedEventsWaiter() = default;
bool URLLoaderInterceptorCallback(
content::URLLoaderInterceptor::RequestParams* params) {
const std::string creative_instance_id =
brave_ads::GetCreativeInstanceIdFromSearchAdsViewedUrl(
params->url_request.url);
if (!creative_instance_id.empty()) {
auto it = std::find(sent_viewed_creative_instance_ids_.begin(),
sent_viewed_creative_instance_ids_.end(),
creative_instance_id);
EXPECT_TRUE(it != sent_viewed_creative_instance_ids_.end())
<< "Not expected creative instance id: " << creative_instance_id;
if (it != sent_viewed_creative_instance_ids_.end()) {
sent_viewed_creative_instance_ids_.erase(it);
}
}
if (sent_viewed_creative_instance_ids_.empty()) {
run_loop_.Quit();
}
return false;
}
void WaitForViewedEvents() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
std::vector<std::string> sent_viewed_creative_instance_ids_;
std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
};
} // namespace
class SearchResultAdTest : public InProcessBrowserTest {
@@ -163,9 +208,9 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, SampleSearchAdMetadata) {
brave_ads::MockAdsService ads_service;
ScopedTestingAdsServiceSetter scoped_setter(profile(), &ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(true));
brave_ads::TriggerSearchResultAdEventCallback trigger_callback;
auto run_loop = std::make_unique<base::RunLoop>();
EXPECT_CALL(ads_service, IsEnabled()).WillOnce(Return(true));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _))
.WillOnce([&run_loop, &trigger_callback](
ads::mojom::SearchResultAdPtr ad_mojom,
@@ -176,10 +221,12 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, SampleSearchAdMetadata) {
run_loop->Quit();
});
SentViewedEventsWaiter sent_viewed_events_waiter(
{"data-creative-instance-id-1", "not-existant"});
LoadTestDataUrl(kAllowedDomain, "/brave_ads/search_result_ad_sample.html");
run_loop->Run();
Mock::VerifyAndClearExpectations(&ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(true));
run_loop = std::make_unique<base::RunLoop>();
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _))
@@ -192,51 +239,27 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, SampleSearchAdMetadata) {
run_loop->Quit();
});
// Continue to trigger ad viewed events even if one of them failed.
std::move(trigger_callback)
.Run(true, "placement-id-1",
.Run(false, "placement-id-1",
ads::mojom::SearchResultAdEventType::kViewed);
run_loop->Run();
Mock::VerifyAndClearExpectations(&ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(true));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _)).Times(0);
std::move(trigger_callback)
.Run(true, "placement-id-2",
ads::mojom::SearchResultAdEventType::kViewed);
}
IN_PROC_BROWSER_TEST_F(SearchResultAdTest, FailedToTriggerSearchResultAdEvent) {
brave_ads::MockAdsService ads_service;
ScopedTestingAdsServiceSetter scoped_setter(profile(), &ads_service);
brave_ads::TriggerSearchResultAdEventCallback trigger_callback;
auto run_loop = std::make_unique<base::RunLoop>();
EXPECT_CALL(ads_service, IsEnabled()).WillOnce(Return(true));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _))
.WillOnce([&run_loop, &trigger_callback](
ads::mojom::SearchResultAdPtr ad_mojom,
const ads::mojom::SearchResultAdEventType event_type,
brave_ads::TriggerSearchResultAdEventCallback callback) {
CheckSampleSearchAdMetadata(ad_mojom, 1);
trigger_callback = std::move(callback);
run_loop->Quit();
});
LoadTestDataUrl(kAllowedDomain, "/brave_ads/search_result_ad_sample.html");
run_loop->Run();
Mock::VerifyAndClearExpectations(&ads_service);
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _)).Times(0);
std::move(trigger_callback)
.Run(false, "placement-id-1",
ads::mojom::SearchResultAdEventType::kViewed);
sent_viewed_events_waiter.WaitForViewedEvents();
}
IN_PROC_BROWSER_TEST_F(SearchResultAdTest, AdsDisabled) {
brave_ads::MockAdsService ads_service;
ScopedTestingAdsServiceSetter scoped_setter(profile(), &ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillOnce(Return(false));
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(false));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _)).Times(0);
base::RunLoop run_loop;
@@ -247,16 +270,20 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, AdsDisabled) {
},
run_loop.QuitClosure()));
SentViewedEventsWaiter sent_viewed_events_waiter(
{"data-creative-instance-id-1", "data-creative-instance-id-1",
"data-creative-instance-id-2", "not-existant"});
LoadTestDataUrl(kAllowedDomain, "/brave_ads/search_result_ad_sample.html");
run_loop.Run();
sent_viewed_events_waiter.WaitForViewedEvents();
}
IN_PROC_BROWSER_TEST_F(SearchResultAdTest, NotAllowedDomain) {
brave_ads::MockAdsService ads_service;
ScopedTestingAdsServiceSetter scoped_setter(profile(), &ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillOnce(Return(true));
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(true));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _)).Times(0);
base::RunLoop run_loop;
@@ -267,16 +294,20 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, NotAllowedDomain) {
},
run_loop.QuitClosure()));
SentViewedEventsWaiter sent_viewed_events_waiter(
{"data-creative-instance-id-1", "data-creative-instance-id-1",
"data-creative-instance-id-2", "not-existant"});
LoadTestDataUrl(kNotAllowedDomain, "/brave_ads/search_result_ad_sample.html");
run_loop.Run();
sent_viewed_events_waiter.WaitForViewedEvents();
}
IN_PROC_BROWSER_TEST_F(SearchResultAdTest, NoSearchAdMetadata) {
brave_ads::MockAdsService ads_service;
ScopedTestingAdsServiceSetter scoped_setter(profile(), &ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillOnce(Return(true));
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(true));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _)).Times(0);
base::RunLoop run_loop;
@@ -296,7 +327,7 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, BrokenSearchAdMetadata) {
brave_ads::MockAdsService ads_service;
ScopedTestingAdsServiceSetter scoped_setter(profile(), &ads_service);
EXPECT_CALL(ads_service, IsEnabled()).WillOnce(Return(true));
EXPECT_CALL(ads_service, IsEnabled()).WillRepeatedly(Return(true));
EXPECT_CALL(ads_service, TriggerSearchResultAdEvent(_, _, _)).Times(0);
base::RunLoop run_loop;
@@ -307,7 +338,28 @@ IN_PROC_BROWSER_TEST_F(SearchResultAdTest, BrokenSearchAdMetadata) {
},
run_loop.QuitClosure()));
SentViewedEventsWaiter sent_viewed_events_waiter(
{"data-creative-instance-id-1"});
LoadTestDataUrl(kAllowedDomain, "/brave_ads/search_result_ad_broken.html");
run_loop.Run();
sent_viewed_events_waiter.WaitForViewedEvents();
}
IN_PROC_BROWSER_TEST_F(SearchResultAdTest, IncognitoBrowser) {
SentViewedEventsWaiter sent_viewed_events_waiter(
{"data-creative-instance-id-1", "data-creative-instance-id-1",
"data-creative-instance-id-2", "not-existant"});
GURL url = https_server()->GetURL(kAllowedDomain,
"/brave_ads/search_result_ad_sample.html");
Browser* incognito_browser = OpenURLOffTheRecord(browser()->profile(), url);
EXPECT_FALSE(GetSearchResultAdService(incognito_browser->profile()));
content::WebContents* web_contents =
incognito_browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::NavigateToURL(web_contents, url));
EXPECT_EQ(url, web_contents->GetVisibleURL());
sent_viewed_events_waiter.WaitForViewedEvents();
}
@@ -0,0 +1,241 @@
/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/content/browser/search_result_ad/search_result_ad_service.h"
#include "base/test/scoped_feature_list.h"
#include "brave/components/brave_ads/browser/mock_ads_service.h"
#include "brave/components/brave_ads/common/features.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/sessions/core/session_id.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using testing::Return;
namespace {
constexpr char kAllowedDomain[] = "https://search.brave.com";
constexpr char kNotAllowedDomain[] = "https://brave.com";
class SearchResultAdServiceTest : public ChromeRenderViewHostTestHarness {
public:
SearchResultAdServiceTest() {
feature_list_.InitAndEnableFeature(
brave_ads::features::kSupportBraveSearchResultAdConfirmationEvents);
search_result_ad_service_ =
std::make_unique<brave_ads::SearchResultAdService>(&ads_service_);
}
~SearchResultAdServiceTest() override = default;
SearchResultAdServiceTest(const SearchResultAdServiceTest&) = delete;
SearchResultAdServiceTest& operator=(const SearchResultAdServiceTest&) =
delete;
protected:
brave_ads::MockAdsService ads_service_;
std::unique_ptr<brave_ads::SearchResultAdService> search_result_ad_service_;
base::test::ScopedFeatureList feature_list_;
};
TEST_F(SearchResultAdServiceTest, BraveAdsDisabledTryTriggerAd) {
EXPECT_CALL(ads_service_, IsEnabled()).WillOnce(Return(false));
SessionID session_id = SessionID::NewUnique();
NavigateAndCommit(GURL(kAllowedDomain));
search_result_ad_service_->OnDidFinishNavigation(session_id);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, NotAllowedDomainTryTriggerAdBeforeRetrieve) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
NavigateAndCommit(GURL(kNotAllowedDomain));
search_result_ad_service_->OnDidFinishNavigation(session_id);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
search_result_ad_service_->MaybeRetrieveSearchResultAd(main_rfh(), session_id,
true);
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, NotAllowedDomainTryTriggerAdAfterRetrieve) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
NavigateAndCommit(GURL(kNotAllowedDomain));
search_result_ad_service_->OnDidFinishNavigation(session_id);
search_result_ad_service_->MaybeRetrieveSearchResultAd(main_rfh(), session_id,
true);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, TabRestoredTryTriggerAdBeforeRetrieve) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
NavigateAndCommit(GURL(kAllowedDomain));
search_result_ad_service_->OnDidFinishNavigation(session_id);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
search_result_ad_service_->MaybeRetrieveSearchResultAd(main_rfh(), session_id,
false);
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, TabRestoredTryTriggerAdAfterRetrieve) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
NavigateAndCommit(GURL(kAllowedDomain));
search_result_ad_service_->OnDidFinishNavigation(session_id);
search_result_ad_service_->MaybeRetrieveSearchResultAd(main_rfh(), session_id,
false);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, UnknownTabTryTriggerAd) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, TryTriggerAdRepeatedNavigation) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
// Simulate navigation was finished.
search_result_ad_service_->OnDidFinishNavigation(session_id);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
// Simulate navigation.
search_result_ad_service_->OnDidFinishNavigation(session_id);
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, TryTriggerAdBeforeTabDeleted) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
// Simulate navigation was finished.
search_result_ad_service_->OnDidFinishNavigation(session_id);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
// Simulate tab deletion before search result JSON-ln loading.
search_result_ad_service_->OnTabClosed(session_id);
run_loop.Run();
}
TEST_F(SearchResultAdServiceTest, TryTriggerAdAfterTabDeleted) {
EXPECT_CALL(ads_service_, IsEnabled()).WillRepeatedly(Return(true));
SessionID session_id = SessionID::NewUnique();
// Simulate navigation was finished.
search_result_ad_service_->OnDidFinishNavigation(session_id);
// Simulate tab deletion before search result JSON-ln loading.
search_result_ad_service_->OnTabClosed(session_id);
base::RunLoop run_loop;
search_result_ad_service_->MaybeTriggerSearchResultAdViewedEvent(
"creative_instance_id", session_id,
base::BindOnce(
[](base::RunLoop* run_loop, bool ad_was_triggered) {
EXPECT_FALSE(ad_was_triggered);
run_loop->Quit();
},
base::Unretained(&run_loop)));
run_loop.Run();
}
} // namespace
+6 -2
View File
@@ -240,6 +240,8 @@ void BindBraveAdsHost(
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
auto* context = frame_host->GetBrowserContext();
auto* profile = Profile::FromBrowserContext(context);
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(frame_host);
mojo::MakeSelfOwnedReceiver(
#if BUILDFLAG(IS_ANDROID)
@@ -247,7 +249,7 @@ void BindBraveAdsHost(
#elif BUILDFLAG(ENABLE_EXTENSIONS)
std::make_unique<brave_ads::BraveAdsHost>(
#endif // BUILDFLAG(IS_ANDROID)
profile),
profile, web_contents),
std::move(receiver));
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(ENABLE_EXTENSIONS)
}
@@ -497,7 +499,9 @@ void BraveContentBrowserClient::RegisterBrowserInterfaceBindersForFrame(
base::BindRepeating(&BindBraveSearchDefaultHost));
}
if (brave_ads::features::IsRequestAdsEnabledApiEnabled()) {
if (brave_ads::features::IsRequestAdsEnabledApiEnabled() ||
base::FeatureList::IsEnabled(
brave_ads::features::kSupportBraveSearchResultAdConfirmationEvents)) {
map->Add<brave_ads::mojom::BraveAdsHost>(
base::BindRepeating(&BindBraveAdsHost));
}
+1
View File
@@ -1,6 +1,7 @@
include_rules = [
"+brave/components/brave_shields/common",
"+brave/components/content_settings/renderer",
"+brave/renderer",
"+media/base/key_system_properties.h",
"+third_party/widevine/cdm/buildflags.h",
]
@@ -4,5 +4,11 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "brave/components/content_settings/renderer/brave_content_settings_agent_impl.h"
#include "brave/renderer/brave_url_loader_throttle_provider.h"
#include "chrome/renderer/url_loader_throttle_provider_impl.h"
#define URLLoaderThrottleProviderImpl BraveURLLoaderThrottleProvider
#include "src/chrome/renderer/chrome_content_renderer_client.cc"
#undef URLLoaderThrottleProviderImpl
@@ -478,6 +478,8 @@ bool ParseJSON(base::StringPiece json,
{ "name": "payment.rewards.bravesoftware.com", "policy": "custom", "mode": "force-https", "pins": "brave"},
{ "name": "payment.rewards.brave.software", "policy": "custom", "mode": "force-https", "pins": "brave"},
{ "name": "rewards.brave.com", "mode": "force-https", "policy": "custom", "pins": "brave"},
{ "name": "search.anonymous.brave.com", "mode": "force-https", "policy": "custom", "pins": "brave"},
{ "name": "search.anonymous.bravesoftware.com", "mode": "force-https", "policy": "custom", "pins": "brave"},
{ "name": "static.ads.brave.com", "mode": "force-https", "policy": "custom", "pins": "brave"},
{ "name": "static.ads.bravesoftware.com", "mode": "force-https", "policy": "custom", "pins": "brave"},
+6 -1
View File
@@ -12,9 +12,14 @@ static_library("common") {
"features.h",
"pref_names.cc",
"pref_names.h",
"search_result_ad_util.cc",
"search_result_ad_util.h",
"switches.cc",
"switches.h",
]
deps = [ "//base" ]
deps = [
"//base",
"//url",
]
}
@@ -1,5 +1,7 @@
module brave_ads.mojom;
interface BraveAdsHost {
MaybeTriggerAdViewedEvent(string creative_instance_id) => (bool event_triggered);
RequestAdsEnabled() => (bool result);
};
@@ -0,0 +1,49 @@
/* Copyright 2022 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 http://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/common/search_result_ad_util.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_set.h"
#include "base/strings/string_piece.h"
#include "url/gurl.h"
#include "url/third_party/mozilla/url_parse.h"
namespace brave_ads {
namespace {
constexpr auto kSearchAdsConfirmationVettedHosts =
base::MakeFixedFlatSet<base::StringPiece>(
{"search.anonymous.brave.com", "search.anonymous.bravesoftware.com"});
constexpr char kSearchAdsViewedPath[] = "/v10/view";
constexpr char kCreativeInstanceIdParameterName[] = "creativeInstanceId";
} // namespace
std::string GetCreativeInstanceIdFromSearchAdsViewedUrl(const GURL& url) {
if (!url.is_valid() || !url.SchemeIs(url::kHttpsScheme) ||
url.path_piece() != kSearchAdsViewedPath || !url.has_query()) {
return std::string();
}
if (!base::Contains(kSearchAdsConfirmationVettedHosts, url.host_piece())) {
return std::string();
}
base::StringPiece query_str = url.query_piece();
url::Component query(0, static_cast<int>(query_str.length())), key, value;
while (url::ExtractQueryKeyValue(query_str.data(), &query, &key, &value)) {
base::StringPiece key_str = query_str.substr(key.begin, key.len);
if (key_str == kCreativeInstanceIdParameterName) {
base::StringPiece value_str = query_str.substr(value.begin, value.len);
return static_cast<std::string>(value_str);
}
}
return std::string();
}
} // namespace brave_ads
@@ -0,0 +1,19 @@
/* Copyright 2022 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 http://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_COMMON_SEARCH_RESULT_AD_UTIL_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_COMMON_SEARCH_RESULT_AD_UTIL_H_
#include <string>
class GURL;
namespace brave_ads {
std::string GetCreativeInstanceIdFromSearchAdsViewedUrl(const GURL& url);
} // namespace brave_ads
#endif // BRAVE_COMPONENTS_BRAVE_ADS_COMMON_SEARCH_RESULT_AD_UTIL_H_
@@ -1,3 +1,8 @@
# Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/.
source_set("search_result_ad") {
sources = [
"search_result_ad_parsing.cc",
@@ -12,6 +17,7 @@ source_set("search_result_ad") {
"//brave/components/brave_ads/common",
"//brave/components/brave_search/common",
"//components/schema_org/common:mojom",
"//components/sessions",
"//content/public/browser",
"//mojo/public/cpp/bindings",
"//services/service_manager/public/cpp",
@@ -6,8 +6,8 @@
#include "brave/components/brave_ads/content/browser/search_result_ad/search_result_ad_parsing.h"
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "base/containers/fixed_flat_set.h"
#include "base/containers/flat_set.h"
@@ -23,7 +23,7 @@ namespace brave_ads {
namespace {
constexpr char kSearchResultAdsListType[] = "Product";
constexpr char kProductType[] = "Product";
constexpr char kSearchResultAdType[] = "SearchResultAd";
constexpr char kContextPropertyName[] = "@context";
@@ -166,12 +166,12 @@ bool SetSearchAdProperty(const schema_org::mojom::PropertyPtr& ad_property,
return false;
}
absl::optional<SearchResultAdsList> ParseSearchResultAdsListEntityProperties(
absl::optional<SearchResultAdMap> ParseSearchResultAdMapEntityProperties(
const schema_org::mojom::EntityPtr& entity) {
DCHECK(entity);
DCHECK_EQ(entity->type, kSearchResultAdsListType);
DCHECK_EQ(entity->type, kProductType);
SearchResultAdsList search_result_ads_list;
SearchResultAdMap search_result_ads;
for (const auto& property : entity->properties) {
if (!property || property->name == kContextPropertyName ||
@@ -188,14 +188,14 @@ absl::optional<SearchResultAdsList> ParseSearchResultAdsListEntityProperties(
if (!property->values->is_entity_values() ||
property->values->get_entity_values().empty()) {
LOG(ERROR) << "Search result ad attributes list is empty";
return SearchResultAdsList();
return SearchResultAdMap();
}
for (const auto& ad_entity : property->values->get_entity_values()) {
if (!ad_entity || ad_entity->type != kSearchResultAdType) {
LOG(ERROR) << "Wrong search result ad type specified: "
<< ad_entity->type;
return SearchResultAdsList();
return SearchResultAdMap();
}
if (property->name == kTypePropertyName) {
@@ -215,14 +215,14 @@ absl::optional<SearchResultAdsList> ParseSearchResultAdsListEntityProperties(
if (it == kSearchResultAdAttributes.end()) {
LOG(ERROR) << "Wrong search result ad attribute specified: "
<< ad_property->name;
return SearchResultAdsList();
return SearchResultAdMap();
}
found_attributes.insert(*it);
if (!SetSearchAdProperty(ad_property, search_result_ad.get())) {
LOG(ERROR) << "Cannot read search result ad attribute value: "
<< ad_property->name;
return SearchResultAdsList();
return SearchResultAdMap();
}
}
@@ -237,31 +237,35 @@ absl::optional<SearchResultAdsList> ParseSearchResultAdsListEntityProperties(
LOG(ERROR) << "Some of search result ad attributes were not specified: "
<< base::JoinString(absent_attributes, ", ");
return SearchResultAdsList();
return SearchResultAdMap();
}
search_result_ads_list.push_back(std::move(search_result_ad));
const std::string creative_instance_id =
search_result_ad->creative_instance_id;
search_result_ads.emplace(creative_instance_id,
std::move(search_result_ad));
}
// Creatives has been parsed.
break;
}
return search_result_ads_list;
return search_result_ads;
}
void LogSearchResultAdsList(const SearchResultAdsList& search_result_ads_list) {
void LogSearchResultAdMap(const SearchResultAdMap& search_result_ads) {
if (!VLOG_IS_ON(1)) {
return;
}
if (search_result_ads_list.empty()) {
if (search_result_ads.empty()) {
VLOG(1) << "Parsed search result ads list is empty.";
return;
}
VLOG(1) << "Parsed search result ads list:";
for (const auto& search_result_ad : search_result_ads_list) {
for (const auto& search_result_ad_pair : search_result_ads) {
const auto& search_result_ad = search_result_ad_pair.second;
VLOG(1) << "Ad with \"" << kDataPlacementId
<< "\": " << search_result_ad->placement_id;
VLOG(1) << " \"" << kDataCreativeInstanceId
@@ -292,26 +296,24 @@ void LogSearchResultAdsList(const SearchResultAdsList& search_result_ads_list) {
} // namespace
SearchResultAdsList ParseWebPageEntities(blink::mojom::WebPagePtr web_page) {
DCHECK(web_page);
SearchResultAdMap ParseWebPageEntities(blink::mojom::WebPagePtr web_page) {
for (const auto& entity : web_page->entities) {
if (entity->type != kSearchResultAdsListType) {
if (entity->type != kProductType) {
continue;
}
absl::optional<SearchResultAdsList> search_result_ads_list =
ParseSearchResultAdsListEntityProperties(entity);
absl::optional<SearchResultAdMap> search_result_ads =
ParseSearchResultAdMapEntityProperties(entity);
if (search_result_ads_list) {
LogSearchResultAdsList(*search_result_ads_list);
return std::move(*search_result_ads_list);
if (search_result_ads) {
LogSearchResultAdMap(*search_result_ads);
return std::move(*search_result_ads);
}
}
VLOG(1) << "No search result ad found.";
return SearchResultAdsList();
return SearchResultAdMap();
}
} // namespace brave_ads
@@ -6,16 +6,17 @@
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CONTENT_BROWSER_SEARCH_RESULT_AD_SEARCH_RESULT_AD_PARSING_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_CONTENT_BROWSER_SEARCH_RESULT_AD_SEARCH_RESULT_AD_PARSING_H_
#include <vector>
#include <map>
#include <string>
#include "brave/vendor/bat-native-ads/include/bat/ads/public/interfaces/ads.mojom.h"
#include "third_party/blink/public/mojom/document_metadata/document_metadata.mojom.h"
namespace brave_ads {
using SearchResultAdsList = std::vector<ads::mojom::SearchResultAdPtr>;
using SearchResultAdMap = std::map<std::string, ads::mojom::SearchResultAdPtr>;
SearchResultAdsList ParseWebPageEntities(blink::mojom::WebPagePtr web_page);
SearchResultAdMap ParseWebPageEntities(blink::mojom::WebPagePtr web_page);
} // namespace brave_ads
@@ -5,9 +5,10 @@
#include <string>
#include <utility>
#include <vector>
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "brave/components/brave_ads/content/browser/search_result_ad/search_result_ad_parsing.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -23,15 +24,15 @@ schema_org::mojom::ValuesPtr CreateVectorValuesPtr(int64_t value) {
return schema_org::mojom::Values::NewLongValues({value});
}
class WebPageConstructor final {
class TestWebPageConstructor final {
public:
explicit WebPageConstructor(int attribute_index_to_skip = -1)
explicit TestWebPageConstructor(int attribute_index_to_skip = -1)
: attribute_index_to_skip_(attribute_index_to_skip) {
web_page_ = CreateWebPage();
}
~WebPageConstructor() = default;
WebPageConstructor(const WebPageConstructor&) = delete;
WebPageConstructor& operator=(const WebPageConstructor&) = delete;
~TestWebPageConstructor() = default;
TestWebPageConstructor(const TestWebPageConstructor&) = delete;
TestWebPageConstructor& operator=(const TestWebPageConstructor&) = delete;
blink::mojom::WebPagePtr GetWebPage() { return std::move(web_page_); }
@@ -56,10 +57,9 @@ class WebPageConstructor final {
}
template <typename T>
void AddStringProperty(
std::vector<schema_org::mojom::PropertyPtr>* properties,
base::StringPiece name,
T value) {
void AddProperty(std::vector<schema_org::mojom::PropertyPtr>* properties,
base::StringPiece name,
T value) {
int index = current_attribute_index_++;
if (index == attribute_index_to_skip_) {
return;
@@ -74,8 +74,8 @@ class WebPageConstructor final {
schema_org::mojom::EntityPtr CreateCreativeEntity() {
const char* kSearchResultAdStringAttributes[] = {
"data-placement-id",
"data-creative-instance-id",
"data-placement-id",
"data-creative-set-id",
"data-campaign-id",
"data-advertiser-id",
@@ -88,17 +88,16 @@ class WebPageConstructor final {
schema_org::mojom::EntityPtr entity = schema_org::mojom::Entity::New();
entity->type = "SearchResultAd";
AddStringProperty<std::string>(&entity->properties, "data-landing-page",
"https://target.url");
AddStringProperty<std::string>(&entity->properties, "data-rewards-value",
"0.5");
AddStringProperty<int64_t>(&entity->properties,
"data-conversion-observation-window-value", 1);
AddProperty<std::string>(&entity->properties, "data-landing-page",
"https://target.url");
AddProperty<std::string>(&entity->properties, "data-rewards-value", "0.5");
AddProperty<int64_t>(&entity->properties,
"data-conversion-observation-window-value", 1);
int index = 0;
for (const auto** it = std::begin(kSearchResultAdStringAttributes);
it != std::end(kSearchResultAdStringAttributes); ++it, ++index) {
AddStringProperty<std::string>(
AddProperty<std::string>(
&entity->properties, *it,
std::string("value") + base::NumberToString(index));
}
@@ -112,11 +111,12 @@ class WebPageConstructor final {
};
TEST(SearchResultAdParsingTest, ValidWebPage) {
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
ASSERT_EQ(ads.size(), 1u);
ads::mojom::SearchResultAdPtr& search_result_ad = ads[0];
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_EQ(ads.size(), 1u);
ads::mojom::SearchResultAdPtr& search_result_ad = ads["value0"];
ASSERT_TRUE(search_result_ad.get());
EXPECT_EQ(search_result_ad->target_url, GURL("https://target.url"));
EXPECT_EQ(search_result_ad->value, 0.5);
@@ -124,8 +124,8 @@ TEST(SearchResultAdParsingTest, ValidWebPage) {
static_cast<size_t>(search_result_ad->conversion->observation_window),
1u);
EXPECT_EQ(search_result_ad->placement_id, "value0");
EXPECT_EQ(search_result_ad->creative_instance_id, "value1");
EXPECT_EQ(search_result_ad->creative_instance_id, "value0");
EXPECT_EQ(search_result_ad->placement_id, "value1");
EXPECT_EQ(search_result_ad->creative_set_id, "value2");
EXPECT_EQ(search_result_ad->campaign_id, "value3");
EXPECT_EQ(search_result_ad->advertiser_id, "value4");
@@ -139,67 +139,67 @@ TEST(SearchResultAdParsingTest, ValidWebPage) {
TEST(SearchResultAdParsingTest, NotValidWebPage) {
{
blink::mojom::WebPagePtr web_page = blink::mojom::WebPage::New();
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
web_page->entities[0]->type = "Not-Product";
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
web_page->entities[0]->properties.clear();
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
property->name = "not-creatives";
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
property->values = schema_org::mojom::Values::NewEntityValues({});
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
property->values = schema_org::mojom::Values::NewStringValues({"creative"});
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
}
TEST(SearchResultAdParsingTest, NotValidAdEntityExtraProperty) {
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
auto& ad_entity = property->values->get_entity_values()[0];
ad_entity->type = "Not-SearchResultAd";
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
WebPageConstructor constructor;
TestWebPageConstructor constructor;
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
auto& ad_entity = property->values->get_entity_values()[0];
@@ -211,7 +211,7 @@ TEST(SearchResultAdParsingTest, NotValidAdEntityExtraProperty) {
schema_org::mojom::Values::NewStringValues({"extra-value"});
ad_entity->properties.push_back(std::move(extra_property));
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
}
@@ -219,9 +219,9 @@ TEST(SearchResultAdParsingTest, NotValidAdEntityExtraProperty) {
TEST(SearchResultAdParsingTest, NotValidAdEntityPropertySkipped) {
constexpr int kSearchResultAdAttributesCount = 12;
for (int index = 0; index < kSearchResultAdAttributesCount; ++index) {
WebPageConstructor constructor(index);
TestWebPageConstructor constructor(index);
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
}
@@ -229,7 +229,7 @@ TEST(SearchResultAdParsingTest, NotValidAdEntityPropertySkipped) {
TEST(SearchResultAdParsingTest, NotValidAdEntityWrongPropertyType) {
{
// Skip "data-landing-page".
WebPageConstructor constructor(0);
TestWebPageConstructor constructor(0);
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
auto& ad_entity = property->values->get_entity_values()[0];
@@ -241,13 +241,13 @@ TEST(SearchResultAdParsingTest, NotValidAdEntityWrongPropertyType) {
schema_org::mojom::Values::NewStringValues({"http://target.url"});
ad_entity->properties.push_back(std::move(extra_property));
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
// Skip "data-rewards-value".
WebPageConstructor constructor(1);
TestWebPageConstructor constructor(1);
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
auto& ad_entity = property->values->get_entity_values()[0];
@@ -259,13 +259,13 @@ TEST(SearchResultAdParsingTest, NotValidAdEntityWrongPropertyType) {
schema_org::mojom::Values::NewStringValues({"0-5"});
ad_entity->properties.push_back(std::move(extra_property));
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
// Skip "data-conversion-observation-window-value".
WebPageConstructor constructor(2);
TestWebPageConstructor constructor(2);
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
auto& ad_entity = property->values->get_entity_values()[0];
@@ -276,24 +276,24 @@ TEST(SearchResultAdParsingTest, NotValidAdEntityWrongPropertyType) {
extra_property->values = schema_org::mojom::Values::NewStringValues({"1"});
ad_entity->properties.push_back(std::move(extra_property));
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
{
// Skip "data-placement-id".
WebPageConstructor constructor(3);
// Skip "data-creative-instance-id".
TestWebPageConstructor constructor(3);
blink::mojom::WebPagePtr web_page = constructor.GetWebPage();
auto& property = web_page->entities[0]->properties[0];
auto& ad_entity = property->values->get_entity_values()[0];
schema_org::mojom::PropertyPtr extra_property =
schema_org::mojom::Property::New();
extra_property->name = "data-placement-id";
extra_property->name = "data-creative-instance-id";
extra_property->values = schema_org::mojom::Values::NewLongValues({101});
ad_entity->properties.push_back(std::move(extra_property));
SearchResultAdsList ads = ParseWebPageEntities(std::move(web_page));
SearchResultAdMap ads = ParseWebPageEntities(std::move(web_page));
EXPECT_TRUE(ads.empty());
}
}
@@ -8,6 +8,7 @@
#include <utility>
#include "base/callback.h"
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "brave/components/brave_ads/browser/ads_service.h"
#include "brave/components/brave_ads/common/features.h"
@@ -18,23 +19,40 @@
namespace brave_ads {
SearchResultAdService::AdViewedEventCallbackInfo::AdViewedEventCallbackInfo() =
default;
SearchResultAdService::AdViewedEventCallbackInfo::AdViewedEventCallbackInfo(
AdViewedEventCallbackInfo&& info) = default;
SearchResultAdService::AdViewedEventCallbackInfo&
SearchResultAdService::AdViewedEventCallbackInfo::operator=(
AdViewedEventCallbackInfo&& info) = default;
SearchResultAdService::AdViewedEventCallbackInfo::~AdViewedEventCallbackInfo() =
default;
SearchResultAdService::SearchResultAdService(AdsService* ads_service)
: ads_service_(ads_service) {}
: ads_service_(ads_service) {
DCHECK(ads_service_);
}
SearchResultAdService::~SearchResultAdService() = default;
void SearchResultAdService::MaybeRetrieveSearchResultAd(
content::RenderFrameHost* render_frame_host) {
content::RenderFrameHost* render_frame_host,
SessionID tab_id,
bool should_trigger_viewed_event) {
DCHECK(ads_service_);
DCHECK(render_frame_host);
DCHECK(tab_id.is_valid());
if (!ads_service_->IsEnabled() ||
if (!should_trigger_viewed_event || !ads_service_->IsEnabled() ||
!base::FeatureList::IsEnabled(
features::kSupportBraveSearchResultAdConfirmationEvents) ||
!brave_search::IsAllowedHost(render_frame_host->GetLastCommittedURL())) {
if (metadata_request_finished_callback_for_testing_) {
std::move(metadata_request_finished_callback_for_testing_).Run();
}
search_result_ads_[tab_id] = SearchResultAdMap();
RunAdViewedEventPendingCallbacks(tab_id, /* ads_fetched */ false);
return;
}
@@ -42,12 +60,61 @@ void SearchResultAdService::MaybeRetrieveSearchResultAd(
render_frame_host->GetRemoteInterfaces()->GetInterface(
document_metadata.BindNewPipeAndPassReceiver());
DCHECK(document_metadata.is_bound());
document_metadata.reset_on_disconnect();
blink::mojom::DocumentMetadata* raw_document_metadata =
document_metadata.get();
raw_document_metadata->GetEntities(
base::BindOnce(&SearchResultAdService::OnRetrieveSearchResultAdEntities,
weak_factory_.GetWeakPtr(), std::move(document_metadata)));
raw_document_metadata->GetEntities(base::BindOnce(
&SearchResultAdService::OnRetrieveSearchResultAdEntities,
weak_factory_.GetWeakPtr(), std::move(document_metadata), tab_id));
}
void SearchResultAdService::OnDidFinishNavigation(SessionID tab_id) {
// Clear the tab state from the previous load.
ResetState(tab_id);
// Now ad viewed events callbacks will be cached before search result JSON-ld
// is loaded and processed.
ad_viewed_event_pending_callbacks_[tab_id] =
std::vector<AdViewedEventCallbackInfo>();
}
void SearchResultAdService::OnTabClosed(SessionID tab_id) {
// Clear the tab state in memory.
ResetState(tab_id);
}
void SearchResultAdService::MaybeTriggerSearchResultAdViewedEvent(
const std::string& creative_instance_id,
SessionID tab_id,
base::OnceCallback<void(bool)> callback) {
DCHECK(ads_service_);
DCHECK(!creative_instance_id.empty());
DCHECK(tab_id.is_valid());
if (!ads_service_->IsEnabled()) {
std::move(callback).Run(/* event_triggered */ false);
return;
}
// Check if search result ad JSON-LD wasn't processed yet.
if (!base::Contains(search_result_ads_, tab_id)) {
// Check if OnDidFinishNavigation was called for tab_id.
if (!base::Contains(ad_viewed_event_pending_callbacks_, tab_id)) {
std::move(callback).Run(/* event_triggered */ false);
return;
}
AdViewedEventCallbackInfo callback_info;
callback_info.creative_instance_id = creative_instance_id;
callback_info.callback = std::move(callback);
ad_viewed_event_pending_callbacks_[tab_id].push_back(
std::move(callback_info));
return;
}
const bool event_triggered =
QueueSearchResultAdViewedEvent(creative_instance_id, tab_id);
std::move(callback).Run(event_triggered);
}
void SearchResultAdService::SetMetadataRequestFinishedCallbackForTesting(
@@ -62,55 +129,108 @@ AdsService* SearchResultAdService::SetAdsServiceForTesting(
return previous_ads_service;
}
void SearchResultAdService::ResetState(SessionID tab_id) {
DCHECK(tab_id.is_valid());
for (auto& callback_info : ad_viewed_event_pending_callbacks_[tab_id]) {
std::move(callback_info.callback).Run(false);
}
ad_viewed_event_pending_callbacks_.erase(tab_id);
search_result_ads_.erase(tab_id);
}
void SearchResultAdService::OnRetrieveSearchResultAdEntities(
mojo::Remote<blink::mojom::DocumentMetadata> document_metadata,
SessionID tab_id,
blink::mojom::WebPagePtr web_page) {
if (metadata_request_finished_callback_for_testing_) {
std::move(metadata_request_finished_callback_for_testing_).Run();
}
if (!web_page) {
search_result_ads_[tab_id] = SearchResultAdMap();
RunAdViewedEventPendingCallbacks(tab_id, /* ads_fetched */ false);
return;
}
SearchResultAdsList search_result_ads =
SearchResultAdMap search_result_ads =
ParseWebPageEntities(std::move(web_page));
std::reverse(search_result_ads.begin(), search_result_ads.end());
TriggerSearchResultAdViewedEvents(std::move(search_result_ads));
search_result_ads_.emplace(tab_id, std::move(search_result_ads));
RunAdViewedEventPendingCallbacks(tab_id, /* ads_fetched */ true);
}
void SearchResultAdService::TriggerSearchResultAdViewedEvents(
SearchResultAdsList search_result_ads) {
DCHECK(ads_service_);
if (search_result_ads.empty()) {
return;
void SearchResultAdService::RunAdViewedEventPendingCallbacks(SessionID tab_id,
bool ads_fetched) {
for (auto& callback_info : ad_viewed_event_pending_callbacks_[tab_id]) {
bool event_triggered = false;
if (ads_fetched) {
event_triggered = QueueSearchResultAdViewedEvent(
callback_info.creative_instance_id, tab_id);
}
if (event_triggered) {
VLOG(1) << "Triggered search result ad viewed event for "
<< callback_info.creative_instance_id;
} else {
VLOG(1) << "Failed to trigger search result ad viewed event for "
<< callback_info.creative_instance_id;
}
std::move(callback_info.callback).Run(event_triggered);
}
ad_viewed_event_pending_callbacks_.erase(tab_id);
}
bool SearchResultAdService::QueueSearchResultAdViewedEvent(
const std::string& creative_instance_id,
SessionID tab_id) {
DCHECK(!creative_instance_id.empty());
DCHECK(tab_id.is_valid());
SearchResultAdMap& ad_map = search_result_ads_[tab_id];
auto it = ad_map.find(creative_instance_id);
if (it == ad_map.end()) {
return false;
}
ad_viewed_event_queue_.push_front(std::move(it->second));
ad_map.erase(creative_instance_id);
TriggerSearchResultAdViewedEventFromQueue();
return true;
}
void SearchResultAdService::TriggerSearchResultAdViewedEventFromQueue() {
DCHECK(ads_service_);
DCHECK(!ad_viewed_event_queue_.empty() ||
!trigger_ad_viewed_event_in_progress_);
if (ad_viewed_event_queue_.empty() || trigger_ad_viewed_event_in_progress_) {
return;
}
trigger_ad_viewed_event_in_progress_ = true;
ads::mojom::SearchResultAdPtr search_result_ad =
std::move(search_result_ads.back());
search_result_ads.pop_back();
std::move(ad_viewed_event_queue_.back());
ad_viewed_event_queue_.pop_back();
ads_service_->TriggerSearchResultAdEvent(
std::move(search_result_ad), ads::mojom::SearchResultAdEventType::kViewed,
base::BindOnce(
&SearchResultAdService::OnTriggerSearchResultAdViewedEvents,
weak_factory_.GetWeakPtr(), std::move(search_result_ads)));
base::BindOnce(&SearchResultAdService::OnTriggerSearchResultAdViewedEvent,
weak_factory_.GetWeakPtr()));
}
void SearchResultAdService::OnTriggerSearchResultAdViewedEvents(
SearchResultAdsList search_result_ads,
void SearchResultAdService::OnTriggerSearchResultAdViewedEvent(
const bool success,
const std::string& placement_id,
ads::mojom::SearchResultAdEventType ad_event_type) {
DCHECK_EQ(ad_event_type, ads::mojom::SearchResultAdEventType::kViewed);
if (!success) {
VLOG(1) << "Failed to trigger search result ad viewed event for "
<< placement_id;
return;
}
trigger_ad_viewed_event_in_progress_ = false;
TriggerSearchResultAdViewedEventFromQueue();
TriggerSearchResultAdViewedEvents(std::move(search_result_ads));
if (!success) {
VLOG(1) << "Error during processing of search result ad event for "
<< placement_id;
}
}
} // namespace brave_ads
@@ -6,6 +6,7 @@
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CONTENT_BROWSER_SEARCH_RESULT_AD_SEARCH_RESULT_AD_SERVICE_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_CONTENT_BROWSER_SEARCH_RESULT_AD_SEARCH_RESULT_AD_SERVICE_H_
#include <map>
#include <string>
#include <vector>
@@ -14,6 +15,7 @@
#include "base/memory/weak_ptr.h"
#include "brave/vendor/bat-native-ads/include/bat/ads/public/interfaces/ads.mojom.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/sessions/core/session_id.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/document_metadata/document_metadata.mojom.h"
@@ -25,6 +27,7 @@ namespace brave_ads {
class AdsService;
// Retrieves search result ads from page and handles viewed/clicked events.
class SearchResultAdService : public KeyedService {
public:
explicit SearchResultAdService(AdsService* ads_service);
@@ -33,28 +36,71 @@ class SearchResultAdService : public KeyedService {
SearchResultAdService(const SearchResultAdService&) = delete;
SearchResultAdService& operator=(const SearchResultAdService&) = delete;
void MaybeRetrieveSearchResultAd(content::RenderFrameHost* render_frame_host);
// Retrieves search result ads from the render frame.
// If should_trigger_viewed_event value is false, then viewed
// events shouldn't be sent to the ads library.
void MaybeRetrieveSearchResultAd(content::RenderFrameHost* render_frame_host,
SessionID tab_id,
bool should_trigger_viewed_event);
// Removes search result ads from the previous page load.
void OnDidFinishNavigation(SessionID tab_id);
// Removes search result ads when closing the tab.
void OnTabClosed(SessionID tab_id);
// Triggers a search result ad viewed event on a specific tab.
void MaybeTriggerSearchResultAdViewedEvent(
const std::string& creative_instance_id,
SessionID tab_id,
base::OnceCallback<void(bool)> callback);
void SetMetadataRequestFinishedCallbackForTesting(base::OnceClosure callback);
AdsService* SetAdsServiceForTesting(AdsService* ads_service);
private:
struct AdViewedEventCallbackInfo {
AdViewedEventCallbackInfo();
AdViewedEventCallbackInfo(AdViewedEventCallbackInfo&& info);
AdViewedEventCallbackInfo& operator=(AdViewedEventCallbackInfo&& info);
~AdViewedEventCallbackInfo();
std::string creative_instance_id;
base::OnceCallback<void(bool)> callback;
};
void ResetState(SessionID tab_id);
void OnRetrieveSearchResultAdEntities(
mojo::Remote<blink::mojom::DocumentMetadata> document_metadata,
SessionID tab_id,
blink::mojom::WebPagePtr web_page);
void TriggerSearchResultAdViewedEvents(
std::vector<ads::mojom::SearchResultAdPtr> search_result_ads);
void RunAdViewedEventPendingCallbacks(SessionID tab_id, bool ads_fetched);
void OnTriggerSearchResultAdViewedEvents(
std::vector<ads::mojom::SearchResultAdPtr> search_result_ads,
bool QueueSearchResultAdViewedEvent(const std::string& creative_instance_id,
SessionID tab_id);
void TriggerSearchResultAdViewedEventFromQueue();
void OnTriggerSearchResultAdViewedEvent(
bool success,
const std::string& placement_id,
ads::mojom::SearchResultAdEventType ad_event_type);
raw_ptr<AdsService> ads_service_ = nullptr;
std::map<SessionID, std::map<std::string, ads::mojom::SearchResultAdPtr>>
search_result_ads_;
std::map<SessionID, std::vector<AdViewedEventCallbackInfo>>
ad_viewed_event_pending_callbacks_;
base::circular_deque<ads::mojom::SearchResultAdPtr> ad_viewed_event_queue_;
bool trigger_ad_viewed_event_in_progress_ = false;
base::OnceClosure metadata_request_finished_callback_for_testing_;
base::WeakPtrFactory<SearchResultAdService> weak_factory_{this};
+6 -1
View File
@@ -4,11 +4,14 @@ source_set("renderer") {
"brave_ads_js_handler.h",
"brave_ads_render_frame_observer.cc",
"brave_ads_render_frame_observer.h",
"search_result_ad_renderer_throttle.cc",
"search_result_ad_renderer_throttle.h",
]
deps = [
"//base",
"//brave/components/brave_ads/common:mojom",
"//brave/components/brave_ads/common",
"//brave/components/brave_search/common",
"//content/public/renderer",
"//gin",
"//mojo/public/cpp/bindings",
@@ -16,4 +19,6 @@ source_set("renderer") {
"//third_party/blink/public/common",
"//v8",
]
public_deps = [ "//brave/components/brave_ads/common:mojom" ]
}
+2
View File
@@ -1,8 +1,10 @@
include_rules = [
"+brave/components/brave_ads/common",
"+brave/components/brave_search/common",
"+content/public/renderer",
"+gin",
"+mojo/public/cpp/bindings",
"+services/network/public/cpp",
"+third_party/blink/public",
"+v8/include",
]
@@ -0,0 +1,127 @@
/* Copyright 2022 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 http://mozilla.org/MPL/2.0/. */
#include "brave/components/brave_ads/renderer/search_result_ad_renderer_throttle.h"
#include <utility>
#include "base/feature_list.h"
#include "brave/components/brave_ads/common/features.h"
#include "brave/components/brave_ads/common/search_result_ad_util.h"
#include "brave/components/brave_search/common/brave_search_utils.h"
#include "content/public/renderer/render_frame.h"
#include "services/network/public/cpp/resource_request.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom-shared.h"
#include "third_party/blink/public/mojom/loader/resource_load_info.mojom-shared.h"
#include "third_party/blink/public/platform/web_security_origin.h"
#include "third_party/blink/public/platform/web_string.h"
#include "third_party/blink/public/platform/web_url.h"
#include "third_party/blink/public/platform/web_url_request.h"
#include "url/gurl.h"
namespace brave_ads {
std::unique_ptr<blink::URLLoaderThrottle>
SearchResultAdRendererThrottle::MaybeCreateThrottle(
int render_frame_id,
const blink::WebURLRequest& request) {
if (request.GetRequestContext() != blink::mojom::RequestContextType::FETCH ||
!base::FeatureList::IsEnabled(
features::kSupportBraveSearchResultAdConfirmationEvents)) {
return nullptr;
}
absl::optional<blink::WebSecurityOrigin> top_frame_origin =
request.TopFrameOrigin();
if (!top_frame_origin) {
return nullptr;
}
const GURL top_frame_origin_url = url::Origin(*top_frame_origin).GetURL();
if (!brave_search::IsAllowedHost(top_frame_origin_url)) {
return nullptr;
}
const GURL requestor_origin_url =
url::Origin(request.RequestorOrigin()).GetURL();
if (!brave_search::IsAllowedHost(requestor_origin_url)) {
return nullptr;
}
const GURL url = static_cast<GURL>(request.Url());
std::string creative_instance_id =
GetCreativeInstanceIdFromSearchAdsViewedUrl(url);
if (creative_instance_id.empty()) {
return nullptr;
}
content::RenderFrame* render_frame =
content::RenderFrame::FromRoutingID(render_frame_id);
if (!render_frame || !render_frame->IsMainFrame()) {
return nullptr;
}
mojo::PendingRemote<brave_ads::mojom::BraveAdsHost> brave_ads_pending_remote;
render_frame->GetBrowserInterfaceBroker()->GetInterface(
brave_ads_pending_remote.InitWithNewPipeAndPassReceiver());
auto throttle = std::make_unique<SearchResultAdRendererThrottle>(
std::move(brave_ads_pending_remote), std::move(creative_instance_id));
return throttle;
}
SearchResultAdRendererThrottle::SearchResultAdRendererThrottle(
mojo::PendingRemote<brave_ads::mojom::BraveAdsHost>
brave_ads_pending_remote,
std::string creative_instance_id)
: brave_ads_pending_remote_(std::move(brave_ads_pending_remote)),
creative_instance_id_(std::move(creative_instance_id)) {
DCHECK(brave_ads_pending_remote_);
DCHECK(!creative_instance_id_.empty());
}
SearchResultAdRendererThrottle::~SearchResultAdRendererThrottle() = default;
void SearchResultAdRendererThrottle::DetachFromCurrentSequence() {}
void SearchResultAdRendererThrottle::WillStartRequest(
network::ResourceRequest* request,
bool* defer) {
DCHECK(request);
DCHECK(request->request_initiator);
DCHECK_EQ(creative_instance_id_,
GetCreativeInstanceIdFromSearchAdsViewedUrl(request->url));
DCHECK(brave_search::IsAllowedHost(request->request_initiator->GetURL()));
DCHECK_EQ(request->resource_type,
static_cast<int>(blink::mojom::ResourceType::kXhr));
DCHECK(request->is_fetch_like_api);
mojo::Remote<brave_ads::mojom::BraveAdsHost> brave_ads_remote(
std::move(brave_ads_pending_remote_));
DCHECK(brave_ads_remote.is_bound());
brave_ads_remote.reset_on_disconnect();
brave_ads::mojom::BraveAdsHost* raw_brave_ads_remote = brave_ads_remote.get();
raw_brave_ads_remote->MaybeTriggerAdViewedEvent(
creative_instance_id_,
base::BindOnce(
&SearchResultAdRendererThrottle::OnMaybeTriggerAdViewedEvent,
weak_factory_.GetWeakPtr(), std::move(brave_ads_remote)));
*defer = true;
}
void SearchResultAdRendererThrottle::OnMaybeTriggerAdViewedEvent(
mojo::Remote<brave_ads::mojom::BraveAdsHost> brave_ads_remote,
bool event_triggered) {
if (event_triggered) {
delegate_->CancelWithError(net::ERR_ABORTED);
} else {
delegate_->Resume();
}
}
} // namespace brave_ads
@@ -0,0 +1,59 @@
/* Copyright 2022 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 http://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_RENDERER_SEARCH_RESULT_AD_RENDERER_THROTTLE_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_RENDERER_SEARCH_RESULT_AD_RENDERER_THROTTLE_H_
#include <memory>
#include <string>
#include "base/memory/weak_ptr.h"
#include "brave/components/brave_ads/common/brave_ads_host.mojom.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
namespace blink {
class WebURLRequest;
} // namespace blink
namespace brave_ads {
// Monitors fetch ad viewed confirmation requests and cancels the request
// if the ad viewed event should be processed by the ads library.
class SearchResultAdRendererThrottle : public blink::URLLoaderThrottle {
public:
static std::unique_ptr<blink::URLLoaderThrottle> MaybeCreateThrottle(
int render_frame_id,
const blink::WebURLRequest& request);
SearchResultAdRendererThrottle(
mojo::PendingRemote<brave_ads::mojom::BraveAdsHost> brave_ads_remote,
std::string creative_instance_id);
~SearchResultAdRendererThrottle() override;
SearchResultAdRendererThrottle(const SearchResultAdRendererThrottle&) =
delete;
SearchResultAdRendererThrottle& operator=(
const SearchResultAdRendererThrottle&) = delete;
// Implements blink::URLLoaderThrottle:
void DetachFromCurrentSequence() override;
void WillStartRequest(network::ResourceRequest* request,
bool* defer) override;
private:
void OnMaybeTriggerAdViewedEvent(
mojo::Remote<brave_ads::mojom::BraveAdsHost> brave_ads_remote,
bool event_triggered);
mojo::PendingRemote<brave_ads::mojom::BraveAdsHost> brave_ads_pending_remote_;
std::string creative_instance_id_;
base::WeakPtrFactory<SearchResultAdRendererThrottle> weak_factory_{this};
};
} // namespace brave_ads
#endif // BRAVE_COMPONENTS_BRAVE_ADS_RENDERER_SEARCH_RESULT_AD_RENDERER_THROTTLE_H_
@@ -0,0 +1,64 @@
/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */
#include "brave/renderer/brave_url_loader_throttle_provider.h"
#include <utility>
#include "base/memory/ptr_util.h"
#include "brave/components/brave_ads/renderer/search_result_ad_renderer_throttle.h"
#include "chrome/renderer/chrome_content_renderer_client.h"
#include "chrome/renderer/url_loader_throttle_provider_impl.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
BraveURLLoaderThrottleProvider::BraveURLLoaderThrottleProvider(
blink::ThreadSafeBrowserInterfaceBrokerProxy* broker,
blink::URLLoaderThrottleProviderType type,
ChromeContentRendererClient* chrome_content_renderer_client)
: provider_type_(type),
url_loader_throttle_provider_impl_(
std::make_unique<URLLoaderThrottleProviderImpl>(
broker,
type,
chrome_content_renderer_client)) {}
BraveURLLoaderThrottleProvider::~BraveURLLoaderThrottleProvider() {}
BraveURLLoaderThrottleProvider::BraveURLLoaderThrottleProvider() {}
std::unique_ptr<blink::URLLoaderThrottleProvider>
BraveURLLoaderThrottleProvider::Clone() {
auto throttle_provider_clone =
base::WrapUnique(new BraveURLLoaderThrottleProvider());
throttle_provider_clone->provider_type_ = provider_type_;
throttle_provider_clone->url_loader_throttle_provider_impl_ =
url_loader_throttle_provider_impl_->Clone();
return throttle_provider_clone;
}
blink::WebVector<std::unique_ptr<blink::URLLoaderThrottle>>
BraveURLLoaderThrottleProvider::CreateThrottles(
int render_frame_id,
const blink::WebURLRequest& request) {
blink::WebVector<std::unique_ptr<blink::URLLoaderThrottle>> throttles =
url_loader_throttle_provider_impl_->CreateThrottles(render_frame_id,
request);
if (provider_type_ == blink::URLLoaderThrottleProviderType::kFrame) {
auto search_result_ad_throttle =
brave_ads::SearchResultAdRendererThrottle::MaybeCreateThrottle(
render_frame_id, request);
if (search_result_ad_throttle) {
throttles.emplace_back(std::move(search_result_ad_throttle));
}
}
return throttles;
}
void BraveURLLoaderThrottleProvider::SetOnline(bool is_online) {
url_loader_throttle_provider_impl_->SetOnline(is_online);
}
@@ -0,0 +1,50 @@
/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */
#ifndef BRAVE_RENDERER_BRAVE_URL_LOADER_THROTTLE_PROVIDER_H_
#define BRAVE_RENDERER_BRAVE_URL_LOADER_THROTTLE_PROVIDER_H_
#include <memory>
#include "third_party/blink/public/platform/url_loader_throttle_provider.h"
class ChromeContentRendererClient;
namespace blink {
class ThreadSafeBrowserInterfaceBrokerProxy;
}
class BraveURLLoaderThrottleProvider : public blink::URLLoaderThrottleProvider {
public:
BraveURLLoaderThrottleProvider(
blink::ThreadSafeBrowserInterfaceBrokerProxy* broker,
blink::URLLoaderThrottleProviderType type,
ChromeContentRendererClient* chrome_content_renderer_client);
BraveURLLoaderThrottleProvider(const BraveURLLoaderThrottleProvider& other) =
delete;
BraveURLLoaderThrottleProvider& operator=(
const BraveURLLoaderThrottleProvider&) = delete;
~BraveURLLoaderThrottleProvider() override;
// blink::URLLoaderThrottleProvider implementation.
std::unique_ptr<blink::URLLoaderThrottleProvider> Clone() override;
blink::WebVector<std::unique_ptr<blink::URLLoaderThrottle>> CreateThrottles(
int render_frame_id,
const blink::WebURLRequest& request) override;
void SetOnline(bool is_online) override;
private:
BraveURLLoaderThrottleProvider();
blink::URLLoaderThrottleProviderType provider_type_ =
blink::URLLoaderThrottleProviderType::kFrame;
std::unique_ptr<blink::URLLoaderThrottleProvider>
url_loader_throttle_provider_impl_;
};
#endif // BRAVE_RENDERER_BRAVE_URL_LOADER_THROTTLE_PROVIDER_H_
+2
View File
@@ -8,6 +8,8 @@ brave_chrome_renderer_sources = [
"//brave/renderer/brave_content_renderer_client.h",
"//brave/renderer/brave_render_thread_observer.cc",
"//brave/renderer/brave_render_thread_observer.h",
"//brave/renderer/brave_url_loader_throttle_provider.cc",
"//brave/renderer/brave_url_loader_throttle_provider.h",
]
brave_chrome_renderer_public_deps = [
+3
View File
@@ -85,6 +85,7 @@ test("brave_unit_tests") {
configs += [ "//chrome/test:disable_thinlto_cache_flags" ]
sources = [
"//brave/browser/brave_ads/search_result_ad/search_result_ad_service_unittest.cc",
"//brave/browser/brave_content_browser_client_unittest.cc",
"//brave/browser/brave_resources_util_unittest.cc",
"//brave/browser/browsing_data/brave_browsing_data_remover_delegate_unittest.cc",
@@ -191,6 +192,8 @@ test("brave_unit_tests") {
"//brave/components/adblock_rust_ffi",
"//brave/components/api_request_helper:api_request_helper_unit_tests",
"//brave/components/brave_adaptive_captcha/buildflags",
"//brave/components/brave_ads/browser:test_support",
"//brave/components/brave_ads/common",
"//brave/components/brave_ads/content/browser/search_result_ad",
"//brave/components/brave_ads/test:brave_ads_unit_tests",
"//brave/components/brave_component_updater/browser",
@@ -24,6 +24,14 @@
]
}
</script>
<script>
const load = () => {
fetch('https://search.anonymous.brave.com/v10/view?creativeInstanceId=data-creative-instance-id-1')
.then(response => {})
.catch((error) => {});
}
window.onload = load;
</script>
</head>
<body></body>
</html>
@@ -40,6 +40,25 @@
]
}
</script>
<script>
const load = () => {
fetch('https://search.anonymous.brave.com/v10/view?creativeInstanceId=data-creative-instance-id-1')
.then(response => {})
.catch((error) => {});
fetch('https://search.anonymous.brave.com/v10/view?creativeInstanceId=data-creative-instance-id-2')
.then(response => {})
.catch((error) => {});
// Somthing went wrong and we've sent view confirmation twice for data-creative-instance-id-1.
fetch('https://search.anonymous.bravesoftware.com/v10/view?creativeInstanceId=data-creative-instance-id-1')
.then(response => {})
.catch((error) => {});
// Somthing went wrong and we've sent not existant view confirmation.
fetch('https://search.anonymous.bravesoftware.com/v10/view?creativeInstanceId=not-existant')
.then(response => {})
.catch((error) => {});
}
window.onload = load;
</script>
</head>
<body></body>
</html>