[ads] Add SmartNTT virtual prefs support on iOS (#29161)

* [ads] Add SmartNTT virtual prefs support on iOS

* [ads] Add SmartNTT virtual prefs tests

* Fix review comments
This commit is contained in:
aseren
2025-05-26 11:55:26 -05:00
committed by GitHub
parent cf034f4d3d
commit c49a4bf000
23 changed files with 725 additions and 191 deletions
+2 -1
View File
@@ -11,6 +11,7 @@ source_set("brave_ads") {
"ads_service_delegate.h",
"ads_service_factory.h",
"analytics/p3a/brave_stats_helper.h",
"virtual_pref_provider_delegate.h",
]
public_deps = [
@@ -47,6 +48,7 @@ source_set("impl") {
"application_state/notification_helper/notification_helper.h",
"application_state/notification_helper/notification_helper_impl.cc",
"application_state/notification_helper/notification_helper_impl.h",
"virtual_pref_provider_delegate.cc",
]
deps = [
@@ -63,7 +65,6 @@ source_set("impl") {
"//brave/components/brave_adaptive_captcha",
"//brave/components/brave_ads/browser/application_state",
"//brave/components/p3a_utils",
"//brave/components/skus/browser",
"//chrome/browser:browser_process",
"//chrome/browser:browser_public_dependencies",
"//chrome/browser:primitives",
-176
View File
@@ -5,31 +5,17 @@
#include "brave/browser/brave_ads/ads_service_delegate.h"
#include <cstddef>
#include <utility>
#include <vector>
#include "base/check_deref.h"
#include "base/json/json_reader.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version.h"
#include "base/version_info/channel.h"
#include "base/version_info/version_info.h"
#include "brave/browser/brave_ads/ad_units/notification_ad/notification_ad_platform_bridge.h"
#include "brave/browser/brave_ads/application_state/notification_helper/notification_helper.h"
#include "brave/browser/ui/brave_ads/notification_ad.h"
#include "brave/components/brave_adaptive_captcha/brave_adaptive_captcha_service.h"
#include "brave/components/brave_ads/core/public/common/locale/locale_util.h"
#include "brave/components/skus/browser/pref_names.h"
#include "build/build_config.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data_resolver_factory.h"
#include "chrome/common/channel_info.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_prepopulate_data_resolver.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/public/cpp/notifier_id.h"
@@ -48,102 +34,7 @@
namespace brave_ads {
namespace {
constexpr char kSkuEnvironmentPrefix[] = "skus:";
constexpr char kSkuOrdersKey[] = "orders";
constexpr char kSkuOrderLocationKey[] = "location";
constexpr char kSkuOrderCreatedAtKey[] = "created_at";
constexpr char kSkuOrderExpiresAtKey[] = "expires_at";
constexpr char kSkuOrderLastPaidAtKey[] = "last_paid_at";
constexpr char kSkuOrderStatusKey[] = "status";
constexpr char kNotificationAdUrlPrefix[] = "https://www.brave.com/ads/?";
int GetVersionComponent(size_t index) {
const base::Version& version = version_info::GetVersion();
const std::vector<uint32_t>& version_components = version.components();
return index < version_components.size()
? static_cast<int>(version_components[index])
: 0;
}
int GetMajorVersion() {
return GetVersionComponent(0);
}
int GetMinorVersion() {
return GetVersionComponent(1);
}
int GetBuildVersion() {
return GetVersionComponent(2);
}
int GetPatchVersion() {
return GetVersionComponent(3);
}
bool IsMobilePlatform() {
std::string_view operating_system_name = version_info::GetOSType();
return operating_system_name == "Android" || operating_system_name == "iOS";
}
std::string StripSkuEnvironmentPrefix(const std::string& environment) {
const size_t pos = environment.find(':');
return environment.substr(pos + 1);
}
std::string NormalizeSkuStatus(const std::string& status) {
// Normalize the status field to use consistent (US) spelling, as the JSON
// source localizes it (e.g., "cancelled" vs "canceled").
return status == "cancelled" ? "canceled" : status;
}
base::Value::Dict ParseSkuOrder(const base::Value::Dict& dict) {
base::Value::Dict order;
if (const std::string* const created_at =
dict.FindString(kSkuOrderCreatedAtKey)) {
order.Set(kSkuOrderCreatedAtKey, *created_at);
}
if (const std::string* const expires_at =
dict.FindString(kSkuOrderExpiresAtKey)) {
order.Set(kSkuOrderExpiresAtKey, *expires_at);
}
if (const std::string* const last_paid_at =
dict.FindString(kSkuOrderLastPaidAtKey)) {
order.Set(kSkuOrderLastPaidAtKey, *last_paid_at);
}
if (const std::string* const status = dict.FindString(kSkuOrderStatusKey)) {
const std::string normalized_status = NormalizeSkuStatus(*status);
order.Set(kSkuOrderStatusKey, normalized_status);
}
return order;
}
base::Value::Dict ParseSkuOrders(const base::Value::Dict& dict) {
base::Value::Dict orders;
for (const auto [/*id*/ _, value] : dict) {
const base::Value::Dict* const order = value.GetIfDict();
if (!order) {
continue;
}
const std::string* const location = order->FindString(kSkuOrderLocationKey);
if (!location || location->empty()) {
continue;
}
orders.Set(*location, ParseSkuOrder(*order));
}
return orders;
}
} // namespace
AdsServiceDelegate::AdsServiceDelegate(
@@ -161,49 +52,6 @@ AdsServiceDelegate::AdsServiceDelegate(
AdsServiceDelegate::~AdsServiceDelegate() {}
std::string AdsServiceDelegate::GetDefaultSearchEngineName() {
auto* prepopulate_data_resolver =
TemplateURLPrepopulateData::ResolverFactory::GetForProfile(&*profile_);
const auto template_url_data = prepopulate_data_resolver->GetFallbackSearch();
const std::u16string& default_search_engine_name =
template_url_data ? template_url_data->short_name() : u"";
return base::UTF16ToUTF8(default_search_engine_name);
}
base::Value::Dict AdsServiceDelegate::GetSkus() const {
base::Value::Dict skus;
if (!local_state_->FindPreference(skus::prefs::kSkusState)) {
// No SKUs in local state.
return skus;
}
const base::Value::Dict& skus_state =
local_state_->GetDict(skus::prefs::kSkusState);
for (const auto [environment, value] : skus_state) {
if (!environment.starts_with(kSkuEnvironmentPrefix)) {
continue;
}
// Deserialize the SKUs data from a JSON string stored in local state into a
// dictionary object for further processing.
std::optional<base::Value::Dict> sku_state =
base::JSONReader::ReadDict(value.GetString());
if (!sku_state) {
continue;
}
const base::Value::Dict* const orders = sku_state->FindDict(kSkuOrdersKey);
if (!orders) {
continue;
}
skus.Set(StripSkuEnvironmentPrefix(environment), ParseSkuOrders(*orders));
}
return skus;
}
void AdsServiceDelegate::OpenNewTabWithUrl(const GURL& url) {
#if BUILDFLAG(IS_ANDROID)
// ServiceTabLauncher can currently only launch new tabs
@@ -327,30 +175,6 @@ bool AdsServiceDelegate::IsFullScreenMode() {
#endif
}
base::Value::Dict AdsServiceDelegate::GetVirtualPrefs() {
return base::Value::Dict()
.Set("[virtual]:browser",
base::Value::Dict()
.Set("build_channel",
version_info::GetChannelString(chrome::GetChannel()))
.Set("version", version_info::GetVersionNumber())
.Set("major_version", GetMajorVersion())
.Set("minor_version", GetMinorVersion())
.Set("build_version", GetBuildVersion())
.Set("patch_version", GetPatchVersion()))
.Set("[virtual]:operating_system",
base::Value::Dict()
.Set("locale", base::Value::Dict()
.Set("language", CurrentLanguageCode())
.Set("region", CurrentCountryCode()))
.Set("is_mobile_platform", IsMobilePlatform())
.Set("name", version_info::GetOSType()))
.Set(
"[virtual]:search_engine",
base::Value::Dict().Set("default_name", GetDefaultSearchEngineName()))
.Set("[virtual]:skus", GetSkus());
}
NotificationDisplayService*
AdsServiceDelegate::GetNotificationDisplayService() {
return NotificationDisplayServiceFactory::GetForProfile(&*profile_);
-7
View File
@@ -11,7 +11,6 @@
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/values.h"
#include "brave/components/brave_ads/core/browser/service/ads_service.h"
#include "components/prefs/pref_service.h"
@@ -42,10 +41,6 @@ class AdsServiceDelegate : public AdsService::Delegate {
~AdsServiceDelegate() override;
std::string GetDefaultSearchEngineName();
base::Value::Dict GetSkus() const;
// AdsService::Delegate:
void MaybeInitNotificationHelper(base::OnceClosure callback) override;
bool CanShowSystemNotificationsWhileBrowserIsBackgrounded() override;
@@ -64,8 +59,6 @@ class AdsServiceDelegate : public AdsService::Delegate {
void OpenNewTabWithUrl(const GURL& url) override;
bool IsFullScreenMode() override;
base::Value::Dict GetVirtualPrefs() override;
private:
NotificationDisplayService* GetNotificationDisplayService();
+2
View File
@@ -15,6 +15,7 @@
#include "brave/browser/brave_ads/device_id/device_id_impl.h"
#include "brave/browser/brave_ads/services/bat_ads_service_factory_impl.h"
#include "brave/browser/brave_ads/tooltips/ads_tooltips_delegate_impl.h"
#include "brave/browser/brave_ads/virtual_pref_provider_delegate.h"
#include "brave/browser/brave_browser_process.h"
#include "brave/browser/brave_rewards/rewards_service_factory.h"
#include "brave/browser/brave_rewards/rewards_util.h"
@@ -98,6 +99,7 @@ AdsServiceFactory::BuildServiceInstanceForBrowserContext(
return std::make_unique<AdsServiceImpl>(
std::move(delegate), profile->GetPrefs(),
g_browser_process->local_state(),
std::make_unique<VirtualPrefProviderDelegate>(*profile),
profile->GetDefaultStoragePartition()
->GetURLLoaderFactoryForBrowserProcess(),
brave::GetChannelName(), profile->GetPath(), CreateAdsTooltipsDelegate(),
@@ -0,0 +1,37 @@
/* Copyright (c) 2025 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/virtual_pref_provider_delegate.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version_info/channel.h"
#include "base/version_info/version_info.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data_resolver_factory.h"
#include "chrome/common/channel_info.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_prepopulate_data_resolver.h"
namespace brave_ads {
VirtualPrefProviderDelegate::VirtualPrefProviderDelegate(Profile& profile)
: profile_(profile) {}
VirtualPrefProviderDelegate::~VirtualPrefProviderDelegate() = default;
std::string VirtualPrefProviderDelegate::GetChannel() const {
return std::string(version_info::GetChannelString(chrome::GetChannel()));
}
std::string VirtualPrefProviderDelegate::GetDefaultSearchEngineName() const {
auto* prepopulate_data_resolver =
TemplateURLPrepopulateData::ResolverFactory::GetForProfile(&*profile_);
const auto template_url_data = prepopulate_data_resolver->GetFallbackSearch();
const std::u16string& default_search_engine_name =
template_url_data ? template_url_data->short_name() : u"";
return base::UTF16ToUTF8(default_search_engine_name);
}
} // namespace brave_ads
@@ -0,0 +1,38 @@
/* Copyright (c) 2025 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_VIRTUAL_PREF_PROVIDER_DELEGATE_H_
#define BRAVE_BROWSER_BRAVE_ADS_VIRTUAL_PREF_PROVIDER_DELEGATE_H_
#include <string>
#include "base/memory/raw_ref.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider.h"
class Profile;
namespace brave_ads {
class VirtualPrefProviderDelegate : public VirtualPrefProvider::Delegate {
public:
explicit VirtualPrefProviderDelegate(Profile& profile);
VirtualPrefProviderDelegate(const VirtualPrefProviderDelegate&) = delete;
VirtualPrefProviderDelegate& operator=(const VirtualPrefProviderDelegate&) =
delete;
~VirtualPrefProviderDelegate() override;
std::string GetChannel() const override;
std::string GetDefaultSearchEngineName() const override;
private:
const raw_ref<Profile> profile_;
};
} // namespace brave_ads
#endif // BRAVE_BROWSER_BRAVE_ADS_VIRTUAL_PREF_PROVIDER_DELEGATE_H_
@@ -56,6 +56,7 @@
#include "ios/chrome/browser/safe_browsing/model/safe_browsing_metrics_collector_factory.h"
#include "ios/chrome/browser/saved_tab_groups/model/tab_group_service_factory.h"
#include "ios/chrome/browser/saved_tab_groups/model/tab_group_sync_service_factory.h"
#include "ios/chrome/browser/search_engines/model/template_url_prepopulate_data_resolver_factory.h"
#include "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#include "ios/chrome/browser/segmentation_platform/model/segmentation_platform_service_factory.h"
#include "ios/chrome/browser/sessions/model/session_restoration_service_factory.h"
@@ -100,6 +101,7 @@ void EnsureProfileKeyedServiceFactoriesBuilt() {
ios::HostContentSettingsMapFactory::GetInstance();
ios::LocalOrSyncableBookmarkSyncServiceFactory::GetInstance();
ios::PasswordManagerLogRouterFactory::GetInstance();
ios::TemplateURLPrepopulateDataResolverFactory::GetInstance();
ios::TemplateURLServiceFactory::GetInstance();
ios::TopSitesFactory::GetInstance();
ios::WebDataServiceFactory::GetInstance();
@@ -6,6 +6,7 @@
#include "brave/components/brave_ads/browser/ads_service_impl.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <utility>
@@ -39,6 +40,7 @@
#include "brave/components/brave_ads/browser/tooltips/ads_tooltips_delegate.h"
#include "brave/components/brave_ads/core/browser/service/ads_service_observer.h"
#include "brave/components/brave_ads/core/browser/service/new_tab_page_ad_prefetcher.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider.h"
#include "brave/components/brave_ads/core/public/ad_units/inline_content_ad/inline_content_ad_value_util.h"
#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_value_util.h"
#include "brave/components/brave_ads/core/public/ad_units/notification_ad/notification_ad_feature.h"
@@ -179,6 +181,8 @@ AdsServiceImpl::AdsServiceImpl(
std::unique_ptr<Delegate> delegate,
PrefService* prefs,
PrefService* local_state,
std::unique_ptr<VirtualPrefProvider::Delegate>
virtual_pref_provider_delegate,
scoped_refptr<network::SharedURLLoaderFactory> url_loader,
std::string_view channel_name,
const base::FilePath& profile_path,
@@ -192,6 +196,9 @@ AdsServiceImpl::AdsServiceImpl(
: AdsService(std::move(delegate)),
prefs_(prefs),
local_state_(local_state),
virtual_pref_provider_(std::make_unique<VirtualPrefProvider>(
local_state_,
std::move(virtual_pref_provider_delegate))),
url_loader_(std::move(url_loader)),
channel_name_(channel_name),
history_service_(history_service),
@@ -1883,7 +1890,7 @@ void AdsServiceImpl::HasLocalStatePrefPath(
}
void AdsServiceImpl::GetVirtualPrefs(GetVirtualPrefsCallback callback) {
std::move(callback).Run(delegate_->GetVirtualPrefs());
std::move(callback).Run(virtual_pref_provider_->GetPrefs());
}
void AdsServiceImpl::Log(const std::string& file,
@@ -26,6 +26,7 @@
#include "brave/components/brave_ads/browser/application_state/background_helper.h"
#include "brave/components/brave_ads/browser/component_updater/resource_component_observer.h"
#include "brave/components/brave_ads/core/browser/service/ads_service.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h"
#include "brave/components/brave_ads/core/public/ads_callback.h"
#include "brave/components/brave_ads/core/public/service/ads_service_callback.h"
@@ -68,7 +69,6 @@ class DeviceId;
class ResourceComponent;
struct NewTabPageAdInfo;
class NewTabPageAdPrefetcher;
class AdsServiceImpl final : public AdsService,
public bat_ads::mojom::BatAdsClient,
public bat_ads::mojom::BatAdsObserver,
@@ -81,6 +81,8 @@ class AdsServiceImpl final : public AdsService,
std::unique_ptr<Delegate> delegate,
PrefService* prefs,
PrefService* local_state,
std::unique_ptr<VirtualPrefProvider::Delegate>
virtual_pref_provider_delegate,
scoped_refptr<network::SharedURLLoaderFactory> url_loader,
std::string_view channel_name,
const base::FilePath& profile_path,
@@ -465,6 +467,8 @@ class AdsServiceImpl final : public AdsService,
const raw_ptr<PrefService> local_state_ = nullptr; // Not owned.
const std::unique_ptr<VirtualPrefProvider> virtual_pref_provider_;
scoped_refptr<network::SharedURLLoaderFactory> url_loader_ = nullptr;
const std::string channel_name_;
@@ -10,6 +10,10 @@ source_set("service") {
"ads_service_observer.h",
"new_tab_page_ad_prefetcher.cc",
"new_tab_page_ad_prefetcher.h",
"virtual_pref_provider.cc",
"virtual_pref_provider.h",
"virtual_pref_provider_util.cc",
"virtual_pref_provider_util.h",
]
public_deps = [
@@ -18,20 +22,35 @@ source_set("service") {
"//brave/components/brave_ads/core/public:headers",
"//brave/components/services/bat_ads/public/interfaces",
"//components/keyed_service/core",
"//components/prefs",
"//mojo/public/cpp/bindings",
]
deps = [
"//base/version_info",
"//brave/components/skus/browser",
]
}
source_set("unit_tests") {
testonly = true
sources = [ "new_tab_page_ad_prefetcher_unittest.cc" ]
sources = [
"new_tab_page_ad_prefetcher_unittest.cc",
"virtual_pref_provider_unittest.cc",
]
deps = [
":service",
":test_support",
"//base",
"//base/test:test_support",
"//base/version_info",
"//brave/components/brave_ads/core",
"//brave/components/brave_ads/core/public:test_support",
"//brave/components/skus/browser",
"//components/prefs:test_support",
"//testing/gmock",
"//testing/gtest",
]
}
@@ -50,8 +50,6 @@ class AdsService : public KeyedService {
virtual void CloseNotificationAd(const std::string& id, bool is_custom) = 0;
virtual void OpenNewTabWithUrl(const GURL& url) = 0;
virtual bool IsFullScreenMode() = 0;
virtual base::Value::Dict GetVirtualPrefs() = 0;
};
explicit AdsService(std::unique_ptr<Delegate> delegate);
@@ -0,0 +1,155 @@
/* Copyright (c) 2025 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/browser/service/virtual_pref_provider.h"
#include <utility>
#include "base/check.h"
#include "base/json/json_reader.h"
#include "base/version_info/version_info.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider_util.h"
#include "brave/components/brave_ads/core/public/common/locale/locale_util.h"
#include "brave/components/skus/browser/pref_names.h"
#include "components/prefs/pref_service.h"
namespace brave_ads {
namespace {
constexpr char kSkuEnvironmentPrefix[] = "skus:";
constexpr char kSkuOrdersKey[] = "orders";
constexpr char kSkuOrderLocationKey[] = "location";
constexpr char kSkuOrderCreatedAtKey[] = "created_at";
constexpr char kSkuOrderExpiresAtKey[] = "expires_at";
constexpr char kSkuOrderLastPaidAtKey[] = "last_paid_at";
constexpr char kSkuOrderStatusKey[] = "status";
std::string StripSkuEnvironmentPrefix(const std::string& environment) {
const size_t pos = environment.find(':');
return environment.substr(pos + 1);
}
std::string NormalizeSkuStatus(const std::string& status) {
// Normalize the status field to use consistent (US) spelling, as the JSON
// source localizes it (e.g., "cancelled" vs "canceled").
return status == "cancelled" ? "canceled" : status;
}
base::Value::Dict ParseSkuOrder(const base::Value::Dict& dict) {
base::Value::Dict order;
if (const std::string* const created_at =
dict.FindString(kSkuOrderCreatedAtKey)) {
order.Set(kSkuOrderCreatedAtKey, *created_at);
}
if (const std::string* const expires_at =
dict.FindString(kSkuOrderExpiresAtKey)) {
order.Set(kSkuOrderExpiresAtKey, *expires_at);
}
if (const std::string* const last_paid_at =
dict.FindString(kSkuOrderLastPaidAtKey)) {
order.Set(kSkuOrderLastPaidAtKey, *last_paid_at);
}
if (const std::string* const status = dict.FindString(kSkuOrderStatusKey)) {
const std::string normalized_status = NormalizeSkuStatus(*status);
order.Set(kSkuOrderStatusKey, normalized_status);
}
return order;
}
base::Value::Dict ParseSkuOrders(const base::Value::Dict& dict) {
base::Value::Dict orders;
for (const auto [/*id*/ _, value] : dict) {
const base::Value::Dict* const order = value.GetIfDict();
if (!order) {
continue;
}
const std::string* const location = order->FindString(kSkuOrderLocationKey);
if (!location || location->empty()) {
continue;
}
orders.Set(*location, ParseSkuOrder(*order));
}
return orders;
}
base::Value::Dict GetSkus(PrefService* local_state) {
base::Value::Dict skus;
if (!local_state->FindPreference(skus::prefs::kSkusState)) {
// No SKUs in local state.
return skus;
}
const base::Value::Dict& skus_state =
local_state->GetDict(skus::prefs::kSkusState);
for (const auto [environment, value] : skus_state) {
if (!environment.starts_with(kSkuEnvironmentPrefix)) {
continue;
}
// Deserialize the SKUs data from a JSON string stored in local state into a
// dictionary object for further processing.
std::optional<base::Value::Dict> sku_state =
base::JSONReader::ReadDict(value.GetString());
if (!sku_state) {
continue;
}
const base::Value::Dict* const orders = sku_state->FindDict(kSkuOrdersKey);
if (!orders) {
continue;
}
skus.Set(StripSkuEnvironmentPrefix(environment), ParseSkuOrders(*orders));
}
return skus;
}
} // namespace
VirtualPrefProvider::VirtualPrefProvider(PrefService* local_state,
std::unique_ptr<Delegate> delegate)
: local_state_(local_state), delegate_(std::move(delegate)) {
CHECK(local_state_);
CHECK(delegate_);
}
VirtualPrefProvider::~VirtualPrefProvider() = default;
base::Value::Dict VirtualPrefProvider::GetPrefs() {
return base::Value::Dict()
.Set("[virtual]:browser",
base::Value::Dict()
.Set("build_channel", delegate_->GetChannel())
.Set("version", version_info::GetVersionNumber())
.Set("major_version", GetMajorVersion())
.Set("minor_version", GetMinorVersion())
.Set("build_version", GetBuildVersion())
.Set("patch_version", GetPatchVersion()))
.Set("[virtual]:operating_system",
base::Value::Dict()
.Set("locale", base::Value::Dict()
.Set("language", CurrentLanguageCode())
.Set("region", CurrentCountryCode()))
.Set("is_mobile_platform", IsMobilePlatform())
.Set("name", version_info::GetOSType()))
.Set("[virtual]:search_engine",
base::Value::Dict().Set("default_name",
delegate_->GetDefaultSearchEngineName()))
.Set("[virtual]:skus", GetSkus(local_state_));
}
} // namespace brave_ads
@@ -0,0 +1,47 @@
/* Copyright (c) 2025 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_BROWSER_SERVICE_VIRTUAL_PREF_PROVIDER_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_VIRTUAL_PREF_PROVIDER_H_
#include <memory>
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/values.h"
class PrefService;
namespace brave_ads {
class VirtualPrefProvider final {
public:
class Delegate {
public:
virtual ~Delegate() = default;
virtual std::string GetChannel() const = 0;
virtual std::string GetDefaultSearchEngineName() const = 0;
};
VirtualPrefProvider(PrefService* local_state,
std::unique_ptr<Delegate> delegate);
VirtualPrefProvider(const VirtualPrefProvider&) = delete;
VirtualPrefProvider& operator=(const VirtualPrefProvider&) = delete;
~VirtualPrefProvider();
base::Value::Dict GetPrefs();
private:
const raw_ptr<PrefService> local_state_ = nullptr; // Not owned.
const std::unique_ptr<Delegate> delegate_;
};
} // namespace brave_ads
#endif // BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_VIRTUAL_PREF_PROVIDER_H_
@@ -0,0 +1,229 @@
/* Copyright (c) 2025 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/browser/service/virtual_pref_provider.h"
#include <memory>
#include <string>
#include <utility>
#include "base/memory/raw_ptr.h"
#include "base/test/values_test_util.h"
#include "base/values.h"
#include "base/version_info/version_info.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider_util.h"
#include "brave/components/brave_ads/core/public/common/locale/scoped_locale_for_testing.h"
#include "brave/components/skus/browser/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
// npm run test -- brave_unit_tests --filter=BraveAds*
namespace brave_ads {
class VirtualPrefProviderDelegateMock : public VirtualPrefProvider::Delegate {
public:
MOCK_METHOD(std::string, GetChannel, (), (const));
MOCK_METHOD(std::string, GetDefaultSearchEngineName, (), (const));
};
class VirtualPrefProviderTest : public ::testing::Test {
public:
VirtualPrefProviderTest() {
auto delegate = std::make_unique<VirtualPrefProviderDelegateMock>();
delegate_ = delegate.get();
provider_ = std::make_unique<VirtualPrefProvider>(&pref_service_,
std::move(delegate));
pref_service_.registry()->RegisterDictionaryPref(skus::prefs::kSkusState);
}
~VirtualPrefProviderTest() override {
// Reset delegate to avoid dangling pointer.
delegate_ = nullptr;
}
TestingPrefServiceSimple& pref_service() { return pref_service_; }
VirtualPrefProviderDelegateMock& delegate() const { return *delegate_; }
VirtualPrefProvider& provider() const { return *provider_; }
private:
TestingPrefServiceSimple pref_service_;
raw_ptr<VirtualPrefProviderDelegateMock> delegate_ = nullptr;
std::unique_ptr<VirtualPrefProvider> provider_;
};
TEST_F(VirtualPrefProviderTest, BrowserBuildChannel) {
// Arrange
EXPECT_CALL(delegate(), GetChannel).WillOnce(testing::Return("release"));
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const std::string* build_channel =
virtual_prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
ASSERT_TRUE(build_channel);
// Assert
EXPECT_EQ(*build_channel, "release");
}
TEST_F(VirtualPrefProviderTest, BrowserVersion) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const std::string* version =
virtual_prefs.FindStringByDottedPath("[virtual]:browser.version");
ASSERT_TRUE(version);
// Assert
EXPECT_EQ(*version, version_info::GetVersionNumber());
}
TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
std::optional<int> major_version =
virtual_prefs.FindIntByDottedPath("[virtual]:browser.major_version");
// Assert
EXPECT_EQ(major_version, GetMajorVersion());
}
TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
std::optional<int> minor_version =
virtual_prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
// Assert
EXPECT_EQ(minor_version, GetMinorVersion());
}
TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
std::optional<int> build_version =
virtual_prefs.FindIntByDottedPath("[virtual]:browser.build_version");
// Assert
EXPECT_EQ(build_version, GetBuildVersion());
}
TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
std::optional<int> patch_version =
virtual_prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
// Assert
EXPECT_EQ(patch_version, GetPatchVersion());
}
TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
// Arrange
const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const std::string* language = virtual_prefs.FindStringByDottedPath(
"[virtual]:operating_system.locale.language");
ASSERT_TRUE(language);
// Assert
EXPECT_EQ(*language, "en");
}
TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
// Arrange
const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const std::string* region = virtual_prefs.FindStringByDottedPath(
"[virtual]:operating_system.locale.region");
ASSERT_TRUE(region);
// Assert
EXPECT_EQ(*region, "KY");
}
TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
std::optional<bool> is_mobile_platform = virtual_prefs.FindBoolByDottedPath(
"[virtual]:operating_system.is_mobile_platform");
// Assert
EXPECT_EQ(is_mobile_platform, IsMobilePlatform());
}
TEST_F(VirtualPrefProviderTest, OperatingSystemName) {
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const std::string* name =
virtual_prefs.FindStringByDottedPath("[virtual]:operating_system.name");
ASSERT_TRUE(name);
// Assert
EXPECT_EQ(*name, version_info::GetOSType());
}
TEST_F(VirtualPrefProviderTest, SearchEngineDefaultName) {
// Arrange
EXPECT_CALL(delegate(), GetDefaultSearchEngineName)
.WillOnce(testing::Return("Brave"));
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const std::string* default_search_engine_name =
virtual_prefs.FindStringByDottedPath(
"[virtual]:search_engine.default_name");
ASSERT_TRUE(default_search_engine_name);
// Assert
EXPECT_EQ(*default_search_engine_name, "Brave");
}
TEST_F(VirtualPrefProviderTest, PrefsWithSkus) {
// Arrange
pref_service().SetDict(skus::prefs::kSkusState,
base::Value::Dict().Set("skus:development", R"JSON({
"orders": {
"f24787ab-7bc3-46b9-bc05-65befb360cb8": {
"created_at": "2023-10-24T16:00:57.902289",
"expires_at": "2023-12-24T17:03:59.030987",
"last_paid_at": "2023-11-24T17:03:59.030987",
"location": "leo.brave.com",
"status": "paid"
}
}
})JSON"));
// Act
const base::Value::Dict virtual_prefs = provider().GetPrefs();
const base::Value::Dict* skus = virtual_prefs.FindDict("[virtual]:skus");
ASSERT_TRUE(skus);
// Assert
const base::Value::Dict expected_skus = base::test::ParseJsonDict(
R"JSON(
{
"development": {
"leo.brave.com": {
"created_at": "2023-10-24T16:00:57.902289",
"expires_at": "2023-12-24T17:03:59.030987",
"last_paid_at": "2023-11-24T17:03:59.030987",
"status": "paid"
}
}
})JSON");
EXPECT_EQ(expected_skus, *skus);
}
} // namespace brave_ads
@@ -0,0 +1,50 @@
/* Copyright (c) 2025 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/browser/service/virtual_pref_provider_util.h"
#include <cstddef>
#include <string_view>
#include <vector>
#include "base/version.h"
#include "base/version_info/version_info.h"
namespace brave_ads {
namespace {
int GetVersionComponent(size_t index) {
const base::Version& version = version_info::GetVersion();
const std::vector<uint32_t>& version_components = version.components();
return index < version_components.size()
? static_cast<int>(version_components[index])
: 0;
}
} // namespace
int GetMajorVersion() {
return GetVersionComponent(0);
}
int GetMinorVersion() {
return GetVersionComponent(1);
}
int GetBuildVersion() {
return GetVersionComponent(2);
}
int GetPatchVersion() {
return GetVersionComponent(3);
}
bool IsMobilePlatform() {
std::string_view operating_system_name = version_info::GetOSType();
return operating_system_name == "Android" || operating_system_name == "iOS";
}
} // namespace brave_ads
@@ -0,0 +1,23 @@
/* Copyright (c) 2025 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_BROWSER_SERVICE_VIRTUAL_PREF_PROVIDER_UTIL_H_
#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_VIRTUAL_PREF_PROVIDER_UTIL_H_
namespace brave_ads {
int GetMajorVersion();
int GetMinorVersion();
int GetBuildVersion();
int GetPatchVersion();
bool IsMobilePlatform();
} // namespace brave_ads
#endif // BRAVE_COMPONENTS_BRAVE_ADS_CORE_BROWSER_SERVICE_VIRTUAL_PREF_PROVIDER_UTIL_H_
@@ -57,3 +57,9 @@ source_set("headers") {
public_deps = [ "//brave/components/brave_ads/core/mojom" ]
}
source_set("test_support") {
testonly = true
sources = [ "common/locale/scoped_locale_for_testing.h" ]
}
+1 -1
View File
@@ -660,7 +660,6 @@ source_set("brave_ads_unit_tests") {
"//brave/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_observer_mock.h",
"//brave/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_unittest.cc",
"//brave/components/brave_ads/core/internal/user_engagement/site_visit/site_visit_util_unittest.cc",
"//brave/components/brave_ads/core/public/common/locale/scoped_locale_for_testing.h",
]
deps = [
@@ -668,6 +667,7 @@ source_set("brave_ads_unit_tests") {
"//brave/components/brave_ads/core",
"//brave/components/brave_ads/core/browser/service:unit_tests",
"//brave/components/brave_ads/core/internal",
"//brave/components/brave_ads/core/public:test_support",
"//brave/components/brave_news/common",
"//brave/components/brave_rewards/core",
"//brave/components/challenge_bypass_ristretto",
+1
View File
@@ -1,4 +1,5 @@
include_rules = [
"+brave/components/brave_ads/core/browser",
"+brave/components/brave_ads/core/mojom",
"+brave/components/brave_ads/core/public",
"+brave/components/brave_news/common",
+16 -1
View File
@@ -28,6 +28,7 @@
#include "base/types/optional_ref.h"
#include "base/values.h"
#import "brave/build/ios/mojom/cpp_transformations.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider.h"
#include "brave/components/brave_ads/core/mojom/brave_ads.mojom.h"
#include "brave/components/brave_ads/core/public/ad_units/inline_content_ad/inline_content_ad_info.h"
#include "brave/components/brave_ads/core/public/ad_units/new_tab_page_ad/new_tab_page_ad_info.h"
@@ -58,6 +59,7 @@
#import "brave/ios/browser/api/common/common_operations.h"
#include "brave/ios/browser/brave_ads/ads_service_factory_ios.h"
#include "brave/ios/browser/brave_ads/ads_service_impl_ios.h"
#include "brave/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.h"
#include "build/build_config.h"
#include "components/prefs/pref_service.h"
#include "ios/chrome/browser/shared/model/application_context/application_context.h"
@@ -110,6 +112,7 @@ constexpr NSString* kAdsResourceComponentMetadataVersion = @".v1";
@interface BraveAds () <AdsClientBridge> {
std::unique_ptr<brave_ads::AdsClientNotifier> adsClientNotifier;
std::unique_ptr<brave_ads::VirtualPrefProvider> virtualPrefProvider;
raw_ptr<brave_ads::AdsServiceImplIOS> adsService;
nw_path_monitor_t networkMonitor;
dispatch_queue_t monitorQueue;
@@ -152,6 +155,12 @@ constexpr NSString* kAdsResourceComponentMetadataVersion = @".v1";
[self initObservers];
adsClientNotifier = std::make_unique<brave_ads::AdsClientNotifier>();
ProfileIOS* profile = [self getLastUsedProfile];
CHECK(profile);
virtualPrefProvider = std::make_unique<brave_ads::VirtualPrefProvider>(
self.localStatePrefService,
std::make_unique<brave_ads::VirtualPrefProviderDelegateIOS>(*profile));
}
return self;
}
@@ -163,6 +172,8 @@ constexpr NSString* kAdsResourceComponentMetadataVersion = @".v1";
[self stopNetworkMonitor];
virtualPrefProvider.reset();
[self deallocAdsClientNotifier];
[self cleanupAdsService];
@@ -1435,7 +1446,11 @@ constexpr NSString* kAdsResourceComponentMetadataVersion = @".v1";
}
- (base::Value::Dict)getVirtualPrefs {
return {};
if (virtualPrefProvider == nullptr) {
return {};
}
return virtualPrefProvider->GetPrefs();
}
- (void)log:(const char*)file
+6
View File
@@ -9,13 +9,19 @@ source_set("brave_ads") {
"ads_service_factory_ios.mm",
"ads_service_impl_ios.h",
"ads_service_impl_ios.mm",
"virtual_pref_provider_delegate_ios.h",
"virtual_pref_provider_delegate_ios.mm",
]
deps = [
"//base/version_info",
"//components/pref_registry",
"//components/prefs",
"//components/search_engines",
"//ios/chrome/browser/search_engines/model",
"//ios/chrome/browser/shared/model/profile",
"//ios/chrome/browser/shared/model/profile:profile_keyed_service_factory",
"//ios/chrome/common",
"//ios/web/public",
"//sql",
]
@@ -0,0 +1,39 @@
/* Copyright (c) 2025 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_IOS_BROWSER_BRAVE_ADS_VIRTUAL_PREF_PROVIDER_DELEGATE_IOS_H_
#define BRAVE_IOS_BROWSER_BRAVE_ADS_VIRTUAL_PREF_PROVIDER_DELEGATE_IOS_H_
#include <string>
#include "base/memory/raw_ref.h"
#include "brave/components/brave_ads/core/browser/service/virtual_pref_provider.h"
class ProfileIOS;
namespace brave_ads {
class VirtualPrefProviderDelegateIOS : public VirtualPrefProvider::Delegate {
public:
explicit VirtualPrefProviderDelegateIOS(ProfileIOS& profile);
VirtualPrefProviderDelegateIOS(const VirtualPrefProviderDelegateIOS&) =
delete;
VirtualPrefProviderDelegateIOS& operator=(
const VirtualPrefProviderDelegateIOS&) = delete;
~VirtualPrefProviderDelegateIOS() override;
std::string GetChannel() const override;
std::string GetDefaultSearchEngineName() const override;
private:
const raw_ref<ProfileIOS> profile_;
};
} // namespace brave_ads
#endif // BRAVE_IOS_BROWSER_BRAVE_ADS_VIRTUAL_PREF_PROVIDER_DELEGATE_IOS_H_
@@ -0,0 +1,38 @@
/* Copyright (c) 2025 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/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version_info/channel.h"
#include "components/search_engines/template_url_data.h"
#include "components/search_engines/template_url_prepopulate_data.h"
#include "components/search_engines/template_url_prepopulate_data_resolver.h"
#include "ios/chrome/browser/search_engines/model/template_url_prepopulate_data_resolver_factory.h"
#include "ios/chrome/browser/shared/model/profile/profile_ios.h"
#include "ios/chrome/common/channel_info.h"
namespace brave_ads {
VirtualPrefProviderDelegateIOS::VirtualPrefProviderDelegateIOS(
ProfileIOS& profile)
: profile_(profile) {}
VirtualPrefProviderDelegateIOS::~VirtualPrefProviderDelegateIOS() = default;
std::string VirtualPrefProviderDelegateIOS::GetChannel() const {
return std::string(version_info::GetChannelString(::GetChannel()));
}
std::string VirtualPrefProviderDelegateIOS::GetDefaultSearchEngineName() const {
auto* prepopulate_data_resolver =
ios::TemplateURLPrepopulateDataResolverFactory::GetForProfile(&*profile_);
const auto template_url_data = prepopulate_data_resolver->GetFallbackSearch();
const std::u16string& default_search_engine_name =
template_url_data ? template_url_data->short_name() : u"";
return base::UTF16ToUTF8(default_search_engine_name);
}
} // namespace brave_ads