Repository: brave/brave-core
Pull request #29161: [ads] Add SmartNTT virtual prefs support on iOS
Author: login: aseren
Labels: name: puLL-Merge
Created: 2025-05-21T15:21:27Z | Merged: 2025-05-26T16:55:26Z

### Examples of virtual prefs with the current PR:

**Virtual prefs on MacOS Desktop developer build:**
```
{
    "[virtual]:browser": {
       "build_channel": "unknown",
       "build_version": 81,
       "major_version": 137,
       "minor_version": 1,
       "patch_version": 4,
       "version": "137.1.81.4"
    },
    "[virtual]:operating_system": {
       "is_mobile_platform": false,
       "locale": {
          "language": "en",
          "region": "KY"
       },
       "name": "Mac OS X"
    },
    "[virtual]:search_engine": {
       "default_name": "Google"
    },
    "[virtual]:skus": {
       "production": {
          "talk.brave.com": {
             "created_at": "2023-07-14T17:25:04.497985",
             "status": "pending"
          },
          "vpn.brave.com": {
             "created_at": "2024-12-19T20:50:26.154097",
             "expires_at": "2025-05-27T20:51:45",
             "last_paid_at": "2025-04-26T21:52:46.619401",
             "status": "paid"
          }
       }
    }
}

```

**Virtual prefs on iOS developer build:**
```
{
    "[virtual]:browser": {
       "build_channel": "unknown",
       "build_version": 81,
       "major_version": 137,
       "minor_version": 1,
       "patch_version": 4,
       "version": "137.1.81.4"
    },
    "[virtual]:operating_system": {
       "is_mobile_platform": true,
       "locale": {
          "language": "en",
          "region": "KY"
       },
       "name": "iOS"
    },
    "[virtual]:search_engine": {
       "default_name": "Google"
    },
    "[virtual]:skus": {
       "production": {
          "talk.brave.com": {
             "created_at": "2023-07-14T17:25:04.497985",
             "status": "pending"
          },
          "vpn.brave.com": {
             "created_at": "2024-12-19T20:50:26.154097",
             "expires_at": "2025-05-27T20:51:45",
             "last_paid_at": "2025-04-26T21:52:46.619401",
             "status": "paid"
          }
       }
    }
}
```

<!-- Add brave-browser issue below that this PR will resolve -->
Resolves https://github.com/brave/brave-browser/issues/46238

## Testing plan

Need to check that virtual prefs for SmartNTT condition matchers work as expected for Desktop, Android and iOS.

Description of virtual prefs: https://docs.google.com/document/d/1JZVxdYVoHypR_dxJO73mpuB4i8WJZNSmzuFjYPeHxaY/edit?tab=t.0#heading=h.vrco4m7g1pco

<!-- CI-related labels that can be applied to this PR:
* CI/run-audit-deps (1) - check for known npm/cargo vulnerabilities (audit_deps)
* CI/run-network-audit (1) - run network-audit
* CI/run-upstream-tests - run Chromium unit and browser tests on Linux and Windows (otherwise only on Linux)
* CI/run-linux-arm64, CI/run-macos-x64, CI/run-windows-arm64, CI/run-windows-x86 - run builds that would otherwise be skipped
* CI/skip - do not run CI builds (except noplatform)
* CI/skip-linux-x64, CI/skip-android, CI/skip-macos-arm64, CI/skip-ios, CI/skip-windows-x64 - skip CI builds for specific platforms
* CI/skip-upstream-tests - do not run Chromium unit, or browser tests (otherwise only on Linux)
* CI/skip-all-linters - do not run presubmit and lint checks
* CI/storybook-url (1) - deploy storybook and provide a unique URL for each build

(1) applied automatically when some files are changed (see: https://github.com/brave/brave-core/blob/master/.github/labeler.yml)
-->

<!--
## Checklist:

- Review design docs
  [Browser design principles](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/chrome_browser_design_principles.md)
  [Style guide](https://chromium.googlesource.com/chromium/src/+/main/styleguide/c++/c++.md)
  [Core principles](https://www.chromium.org/developers/core-principles/)
- Ensure there are (tests)[https://www.chromium.org/developers/testing/]. Unit test as much as possible (including edge cases), but also include browser tests covering high level functionality.
- Ensure that there are comments explaining what classes/methods are/do. The "why" is often more important than the "what" in comments. Also update any relevant docs (moving docs from wiki to brave-core if necessary).
- Request security or other review (third-party libraries, rust code, etc...) if applicable [security/privacy review is needed](https://github.com/brave/brave-browser/wiki/Security-reviews) [other review](https://github.com/brave/reviews/issues/new/choose)
  Also see [adding third-party libraries](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/adding_to_third_party.md) for general guidelines on using third party code
- Maks sure there is a [ticket](https://github.com/brave/brave-browser/issues) for your issue
- Use Github [auto-closing keywords](https://docs.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue) in the PR description above
- Write a good [PR/commit description](https://google.github.io/eng-practices/review/developer/cl-descriptions.html)
- Squash any review feedback or "fixup" commits before merge, so that history is a record of what happened in the repo, not your PR
- Add appropriate labels (`QA/Yes` or `QA/No`; `release-notes/include` or `release-notes/exclude`; `OS/...`) to the associated issue
- Checked the PR locally:
  * `npm run test -- brave_browser_tests`, `npm run test -- brave_unit_tests` [wiki](https://github.com/brave/brave-browser/wiki/Tests)
  * `npm run presubmit` [wiki](https://github.com/brave/brave-browser/wiki/Presubmit-checks), `npm run gn_check`, `npm run tslint`
- Run `git rebase master` (if needed)
-->



Diff:
diff --git a/browser/brave_ads/BUILD.gn b/browser/brave_ads/BUILD.gn
index 91bb22265325..c97955341e3a 100644
--- a/browser/brave_ads/BUILD.gn
+++ b/browser/brave_ads/BUILD.gn
@@ -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",
diff --git a/browser/brave_ads/ads_service_delegate.cc b/browser/brave_ads/ads_service_delegate.cc
index bb7b9c36005a..895a3f673b79 100644
--- a/browser/brave_ads/ads_service_delegate.cc
+++ b/browser/brave_ads/ads_service_delegate.cc
@@ -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_);
diff --git a/browser/brave_ads/ads_service_delegate.h b/browser/brave_ads/ads_service_delegate.h
index dfe26a4538cd..07af65a78ab4 100644
--- a/browser/brave_ads/ads_service_delegate.h
+++ b/browser/brave_ads/ads_service_delegate.h
@@ -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();
 
diff --git a/browser/brave_ads/ads_service_factory.cc b/browser/brave_ads/ads_service_factory.cc
index 15fd6ffac69b..62a53367fa4c 100644
--- a/browser/brave_ads/ads_service_factory.cc
+++ b/browser/brave_ads/ads_service_factory.cc
@@ -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(),
diff --git a/browser/brave_ads/virtual_pref_provider_delegate.cc b/browser/brave_ads/virtual_pref_provider_delegate.cc
new file mode 100644
index 000000000000..cc14b8537a6f
--- /dev/null
+++ b/browser/brave_ads/virtual_pref_provider_delegate.cc
@@ -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
diff --git a/browser/brave_ads/virtual_pref_provider_delegate.h b/browser/brave_ads/virtual_pref_provider_delegate.h
new file mode 100644
index 000000000000..0dfb9f40b866
--- /dev/null
+++ b/browser/brave_ads/virtual_pref_provider_delegate.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/. */
+
+#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_
diff --git a/chromium_src/ios/chrome/browser/profile/model/keyed_service_factories.mm b/chromium_src/ios/chrome/browser/profile/model/keyed_service_factories.mm
index 77fd7ae5c29a..80939f544393 100644
--- a/chromium_src/ios/chrome/browser/profile/model/keyed_service_factories.mm
+++ b/chromium_src/ios/chrome/browser/profile/model/keyed_service_factories.mm
@@ -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();
diff --git a/components/brave_ads/browser/ads_service_impl.cc b/components/brave_ads/browser/ads_service_impl.cc
index f2974a16f1c3..e2341f3bd72a 100644
--- a/components/brave_ads/browser/ads_service_impl.cc
+++ b/components/brave_ads/browser/ads_service_impl.cc
@@ -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,
diff --git a/components/brave_ads/browser/ads_service_impl.h b/components/brave_ads/browser/ads_service_impl.h
index 2fe5b3b967a3..0fa88d9adbb1 100644
--- a/components/brave_ads/browser/ads_service_impl.h
+++ b/components/brave_ads/browser/ads_service_impl.h
@@ -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_;
diff --git a/components/brave_ads/core/browser/service/BUILD.gn b/components/brave_ads/core/browser/service/BUILD.gn
index 646078cbce14..04cc3a904f05 100644
--- a/components/brave_ads/core/browser/service/BUILD.gn
+++ b/components/brave_ads/core/browser/service/BUILD.gn
@@ -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",
   ]
 }
diff --git a/components/brave_ads/core/browser/service/ads_service.h b/components/brave_ads/core/browser/service/ads_service.h
index 9f4d0b5916d3..635d6eb76358 100644
--- a/components/brave_ads/core/browser/service/ads_service.h
+++ b/components/brave_ads/core/browser/service/ads_service.h
@@ -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);
diff --git a/components/brave_ads/core/browser/service/virtual_pref_provider.cc b/components/brave_ads/core/browser/service/virtual_pref_provider.cc
new file mode 100644
index 000000000000..b4244b86509b
--- /dev/null
+++ b/components/brave_ads/core/browser/service/virtual_pref_provider.cc
@@ -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
diff --git a/components/brave_ads/core/browser/service/virtual_pref_provider.h b/components/brave_ads/core/browser/service/virtual_pref_provider.h
new file mode 100644
index 000000000000..5f19dd7f8554
--- /dev/null
+++ b/components/brave_ads/core/browser/service/virtual_pref_provider.h
@@ -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_
diff --git a/components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc b/components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
new file mode 100644
index 000000000000..3720ccecb5d4
--- /dev/null
+++ b/components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
@@ -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
diff --git a/components/brave_ads/core/browser/service/virtual_pref_provider_util.cc b/components/brave_ads/core/browser/service/virtual_pref_provider_util.cc
new file mode 100644
index 000000000000..c01b3c2a51a3
--- /dev/null
+++ b/components/brave_ads/core/browser/service/virtual_pref_provider_util.cc
@@ -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
diff --git a/components/brave_ads/core/browser/service/virtual_pref_provider_util.h b/components/brave_ads/core/browser/service/virtual_pref_provider_util.h
new file mode 100644
index 000000000000..e1c1391eddcf
--- /dev/null
+++ b/components/brave_ads/core/browser/service/virtual_pref_provider_util.h
@@ -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_
diff --git a/components/brave_ads/core/public/BUILD.gn b/components/brave_ads/core/public/BUILD.gn
index f33bab27b748..ee1bd90919fc 100644
--- a/components/brave_ads/core/public/BUILD.gn
+++ b/components/brave_ads/core/public/BUILD.gn
@@ -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" ]
+}
diff --git a/components/brave_ads/core/test/BUILD.gn b/components/brave_ads/core/test/BUILD.gn
index be80da0c70a3..5279951d377d 100644
--- a/components/brave_ads/core/test/BUILD.gn
+++ b/components/brave_ads/core/test/BUILD.gn
@@ -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",
diff --git a/ios/browser/api/ads/DEPS b/ios/browser/api/ads/DEPS
index 6e01e01d9367..13d4340b5f7d 100644
--- a/ios/browser/api/ads/DEPS
+++ b/ios/browser/api/ads/DEPS
@@ -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",
diff --git a/ios/browser/api/ads/brave_ads.mm b/ios/browser/api/ads/brave_ads.mm
index e0360d9c6840..898e8874cd4a 100644
--- a/ios/browser/api/ads/brave_ads.mm
+++ b/ios/browser/api/ads/brave_ads.mm
@@ -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 @@ - (instancetype)initWithNewTabPageAdInfo:
 
 @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 @@ - (instancetype)initWithStateStoragePath:(NSString*)path {
     [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 @@ - (void)dealloc {
 
   [self stopNetworkMonitor];
 
+  virtualPrefProvider.reset();
+
   [self deallocAdsClientNotifier];
 
   [self cleanupAdsService];
@@ -1435,7 +1446,11 @@ - (bool)hasLocalStatePrefPath:(const std::string&)path {
 }
 
 - (base::Value::Dict)getVirtualPrefs {
-  return {};
+  if (virtualPrefProvider == nullptr) {
+    return {};
+  }
+
+  return virtualPrefProvider->GetPrefs();
 }
 
 - (void)log:(const char*)file
diff --git a/ios/browser/brave_ads/BUILD.gn b/ios/browser/brave_ads/BUILD.gn
index 8b5ee0b67990..31542d8117a8 100644
--- a/ios/browser/brave_ads/BUILD.gn
+++ b/ios/browser/brave_ads/BUILD.gn
@@ -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",
   ]
diff --git a/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.h b/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.h
new file mode 100644
index 000000000000..f30f00bd5e15
--- /dev/null
+++ b/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.h
@@ -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_
diff --git a/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm b/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
new file mode 100644
index 000000000000..9009adcbf631
--- /dev/null
+++ b/ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
@@ -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


Reviews:
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-21T15:42:57Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T04:03:44Z
user: login: tmancey
body: LGTM (see comments and DM)
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-23T12:23:57Z
user: login: kylehickinson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T14:49:30Z
user: login: kylehickinson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T14:49:51Z
user: login: kylehickinson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T14:50:15Z
user: login: tmancey
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T14:53:05Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T15:50:21Z
user: login: kylehickinson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T19:08:44Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T20:33:40Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-23T23:50:11Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T00:00:08Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T00:03:34Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T00:03:38Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T00:03:42Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-24T00:03:46Z
user: login: iefremov
body: chromium_src and DEPS lgtm
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-26T08:09:24Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-26T14:41:50Z
user: login: kylehickinson
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-05-26T14:43:13Z
user: login: aseren
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-05-26T15:42:29Z

Review comments:
diff_hunk: @@ -1435,7 +1436,19 @@ - (bool)hasLocalStatePrefPath:(const std::string&)path {
 }
 
 - (base::Value::Dict)getVirtualPrefs {
-  return {};
+  return base::Value::Dict()
path: ios/browser/api/ads/brave_ads.mm
user: login: tmancey
body: This is missing several, is that intentional? Can we not use the same code as desktop?
created_at: 2025-05-21T15:42:57Z
author_association: COLLABORATOR
diff_hunk: @@ -1435,7 +1436,19 @@ - (bool)hasLocalStatePrefPath:(const std::string&)path {
 }
 
 - (base::Value::Dict)getVirtualPrefs {
-  return {};
+  return base::Value::Dict()
path: ios/browser/api/ads/brave_ads.mm
user: login: aseren
body: Fixed
created_at: 2025-05-23T04:03:44Z
author_association: COLLABORATOR
diff_hunk: @@ -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.h"
+#include "base/version_info/version_info.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 prefs = provider().GetVirtualPrefs();
+  const std::string* build_channel =
+      prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
+
+  // Assert
+  ASSERT_TRUE(build_channel);
+  EXPECT_EQ(*build_channel, "release");
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* version =
+      prefs.FindStringByDottedPath("[virtual]:browser.version");
+
+  // Assert
+  ASSERT_TRUE(version);
+  EXPECT_EQ(*version, version_info::GetVersionNumber());
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> major_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.major_version");
+
+  // Assert
+  EXPECT_EQ(major_version, version_info::GetVersion().components()[0]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> minor_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
+
+  // Assert
+  EXPECT_EQ(minor_version, version_info::GetVersion().components()[1]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> build_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.build_version");
+
+  // Assert
+  EXPECT_EQ(build_version, version_info::GetVersion().components()[2]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> patch_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
+
+  // Assert
+  EXPECT_EQ(patch_version, version_info::GetVersion().components()[3]);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
+  // Arrange
+  const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* language = prefs.FindStringByDottedPath(
+      "[virtual]:operating_system.locale.language");
+
+  // Assert
+  ASSERT_TRUE(language);
+  EXPECT_EQ(*language, "en");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
+  // Arrange
+  const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* region =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.locale.region");
+
+  // Assert
+  ASSERT_TRUE(region);
+  EXPECT_EQ(*region, "KY");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<bool> is_mobile_platform = prefs.FindBoolByDottedPath(
+      "[virtual]:operating_system.is_mobile_platform");
+
+  // Assert
+  ASSERT_TRUE(is_mobile_platform);
path: components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
user: login: tmancey
body: Is this not only checking if the path exists and not the value?
created_at: 2025-05-23T11:59:15Z
author_association: COLLABORATOR
diff_hunk: @@ -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.h"
+#include "base/version_info/version_info.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 prefs = provider().GetVirtualPrefs();
+  const std::string* build_channel =
+      prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
+
+  // Assert
+  ASSERT_TRUE(build_channel);
+  EXPECT_EQ(*build_channel, "release");
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* version =
+      prefs.FindStringByDottedPath("[virtual]:browser.version");
+
+  // Assert
+  ASSERT_TRUE(version);
+  EXPECT_EQ(*version, version_info::GetVersionNumber());
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> major_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.major_version");
+
+  // Assert
+  EXPECT_EQ(major_version, version_info::GetVersion().components()[0]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> minor_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
+
+  // Assert
+  EXPECT_EQ(minor_version, version_info::GetVersion().components()[1]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> build_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.build_version");
+
+  // Assert
+  EXPECT_EQ(build_version, version_info::GetVersion().components()[2]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> patch_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
+
+  // Assert
+  EXPECT_EQ(patch_version, version_info::GetVersion().components()[3]);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
+  // Arrange
+  const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* language = prefs.FindStringByDottedPath(
+      "[virtual]:operating_system.locale.language");
+
+  // Assert
+  ASSERT_TRUE(language);
+  EXPECT_EQ(*language, "en");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
+  // Arrange
+  const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* region =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.locale.region");
+
+  // Assert
+  ASSERT_TRUE(region);
+  EXPECT_EQ(*region, "KY");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<bool> is_mobile_platform = prefs.FindBoolByDottedPath(
+      "[virtual]:operating_system.is_mobile_platform");
+
+  // Assert
+  ASSERT_TRUE(is_mobile_platform);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemName) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
path: components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
user: login: tmancey
body: Nit: virtual_prefs
created_at: 2025-05-23T11:59:49Z
author_association: COLLABORATOR
diff_hunk: @@ -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.h"
+#include "base/version_info/version_info.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 prefs = provider().GetVirtualPrefs();
+  const std::string* build_channel =
+      prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
+
+  // Assert
+  ASSERT_TRUE(build_channel);
+  EXPECT_EQ(*build_channel, "release");
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* version =
+      prefs.FindStringByDottedPath("[virtual]:browser.version");
+
+  // Assert
+  ASSERT_TRUE(version);
+  EXPECT_EQ(*version, version_info::GetVersionNumber());
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> major_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.major_version");
+
+  // Assert
+  EXPECT_EQ(major_version, version_info::GetVersion().components()[0]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> minor_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
+
+  // Assert
+  EXPECT_EQ(minor_version, version_info::GetVersion().components()[1]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> build_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.build_version");
+
+  // Assert
+  EXPECT_EQ(build_version, version_info::GetVersion().components()[2]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> patch_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
+
+  // Assert
+  EXPECT_EQ(patch_version, version_info::GetVersion().components()[3]);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
+  // Arrange
+  const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* language = prefs.FindStringByDottedPath(
+      "[virtual]:operating_system.locale.language");
+
+  // Assert
+  ASSERT_TRUE(language);
+  EXPECT_EQ(*language, "en");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
+  // Arrange
+  const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* region =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.locale.region");
+
+  // Assert
+  ASSERT_TRUE(region);
+  EXPECT_EQ(*region, "KY");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<bool> is_mobile_platform = prefs.FindBoolByDottedPath(
+      "[virtual]:operating_system.is_mobile_platform");
+
+  // Assert
+  ASSERT_TRUE(is_mobile_platform);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemName) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* name =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.name");
+
+  // Assert
+  ASSERT_TRUE(name);
path: components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
user: login: tmancey
body: nit: move to where this value is assigned (same for others)
created_at: 2025-05-23T12:00:34Z
author_association: COLLABORATOR
diff_hunk: @@ -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_->GetVirtualPrefs());
path: components/brave_ads/browser/ads_service_impl.cc
user: login: tmancey
body: GetPrefs instead of GetVirtualPrefs
created_at: 2025-05-23T12:10:30Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,186 @@
+/* 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 <cstddef>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "base/json/json_reader.h"
+#include "base/version.h"
+#include "base/version_info/version_info.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";
+
+int GetVersionComponent(size_t index) {
path: components/brave_ads/core/browser/service/virtual_pref_provider.cc
user: login: tmancey
body: Tests could maybe call this util rather components()[index], your thoughts?
created_at: 2025-05-23T12:12:44Z
author_association: COLLABORATOR
diff_hunk: @@ -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 =
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: tmancey
body: Does Brave iOS use the chromium code to select the default engine?
created_at: 2025-05-23T12:19:15Z
author_association: COLLABORATOR
diff_hunk: @@ -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()));
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: tmancey
body: Does Brave iOS use the Chromium code for build channel?
created_at: 2025-05-23T12:19:53Z
author_association: COLLABORATOR
diff_hunk: @@ -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()));
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: kylehickinson
body: @tmancey Yes
created_at: 2025-05-23T14:49:30Z
author_association: COLLABORATOR
diff_hunk: @@ -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 =
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: kylehickinson
body: @tmancey No
created_at: 2025-05-23T14:49:51Z
author_association: COLLABORATOR
diff_hunk: @@ -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 =
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: kylehickinson
body: This will most likely have to bridge to Swift side to get the value
created_at: 2025-05-23T14:50:15Z
author_association: COLLABORATOR
diff_hunk: @@ -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()));
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: tmancey
body: Great, thanks
created_at: 2025-05-23T14:53:05Z
author_association: COLLABORATOR
diff_hunk: @@ -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 =
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: aseren
body: Gotcha, thanks! So this part should be re-written then. I'm converting the current PR to Draft until this part is finished.
created_at: 2025-05-23T15:50:21Z
author_association: COLLABORATOR
diff_hunk: @@ -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) {}
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: kylehickinson
body: Probably want to pass in a `raw_ptr` instead?
created_at: 2025-05-23T19:08:44Z
author_association: COLLABORATOR
diff_hunk: @@ -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) {}
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: aseren
body: we have CHECK for `profile` arg here: https://github.com/brave/brave-core/pull/29161/files#diff-906ac001c79dc660c0f59daf19d164ffa7c41255f867f6a122cb601268a1cefdR160.

So `profile` arg cannot be null and we use `raw_ref` here instead of `raw_ptr` which is `A smart pointer for a pointer which can not be null`. This allows us not to write any conditional logic or CHECKs to make sure that pointer is not null.
created_at: 2025-05-23T20:33:40Z
author_association: COLLABORATOR
diff_hunk: @@ -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.h"
+#include "base/version_info/version_info.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 prefs = provider().GetVirtualPrefs();
+  const std::string* build_channel =
+      prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
+
+  // Assert
+  ASSERT_TRUE(build_channel);
+  EXPECT_EQ(*build_channel, "release");
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* version =
+      prefs.FindStringByDottedPath("[virtual]:browser.version");
+
+  // Assert
+  ASSERT_TRUE(version);
+  EXPECT_EQ(*version, version_info::GetVersionNumber());
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> major_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.major_version");
+
+  // Assert
+  EXPECT_EQ(major_version, version_info::GetVersion().components()[0]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> minor_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
+
+  // Assert
+  EXPECT_EQ(minor_version, version_info::GetVersion().components()[1]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> build_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.build_version");
+
+  // Assert
+  EXPECT_EQ(build_version, version_info::GetVersion().components()[2]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> patch_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
+
+  // Assert
+  EXPECT_EQ(patch_version, version_info::GetVersion().components()[3]);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
+  // Arrange
+  const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* language = prefs.FindStringByDottedPath(
+      "[virtual]:operating_system.locale.language");
+
+  // Assert
+  ASSERT_TRUE(language);
+  EXPECT_EQ(*language, "en");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
+  // Arrange
+  const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* region =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.locale.region");
+
+  // Assert
+  ASSERT_TRUE(region);
+  EXPECT_EQ(*region, "KY");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<bool> is_mobile_platform = prefs.FindBoolByDottedPath(
+      "[virtual]:operating_system.is_mobile_platform");
+
+  // Assert
+  ASSERT_TRUE(is_mobile_platform);
path: components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-05-23T23:50:11Z
author_association: COLLABORATOR
diff_hunk: @@ -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 =
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: aseren
body: Currently `VirtualPrefProviderDelegateIOS::GetDefaultSearchEngineName` function uses Brave chromium override to get default search engine. The list of default search engines per regions is here: https://github.com/brave/brave-core/blob/master/chromium_src/components/regional_capabilities/regional_capabilities_utils.cc#L641

And it is the same as default search engines list for iOS from here: https://github.com/brave/brave-core/blob/master/ios/brave-ios/Sources/Brave/Frontend/Browser/Search/InitialSearchEngines.swift#L98.

So basing on discussion in DM with @kylehickinson and it was decided that we can go with the current implementation because these lists will always be same and eventually iOS will move to chromium implementation.
created_at: 2025-05-24T00:00:08Z
author_association: COLLABORATOR
diff_hunk: @@ -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.h"
+#include "base/version_info/version_info.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 prefs = provider().GetVirtualPrefs();
+  const std::string* build_channel =
+      prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
+
+  // Assert
+  ASSERT_TRUE(build_channel);
+  EXPECT_EQ(*build_channel, "release");
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* version =
+      prefs.FindStringByDottedPath("[virtual]:browser.version");
+
+  // Assert
+  ASSERT_TRUE(version);
+  EXPECT_EQ(*version, version_info::GetVersionNumber());
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> major_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.major_version");
+
+  // Assert
+  EXPECT_EQ(major_version, version_info::GetVersion().components()[0]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> minor_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
+
+  // Assert
+  EXPECT_EQ(minor_version, version_info::GetVersion().components()[1]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> build_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.build_version");
+
+  // Assert
+  EXPECT_EQ(build_version, version_info::GetVersion().components()[2]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> patch_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
+
+  // Assert
+  EXPECT_EQ(patch_version, version_info::GetVersion().components()[3]);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
+  // Arrange
+  const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* language = prefs.FindStringByDottedPath(
+      "[virtual]:operating_system.locale.language");
+
+  // Assert
+  ASSERT_TRUE(language);
+  EXPECT_EQ(*language, "en");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
+  // Arrange
+  const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* region =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.locale.region");
+
+  // Assert
+  ASSERT_TRUE(region);
+  EXPECT_EQ(*region, "KY");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<bool> is_mobile_platform = prefs.FindBoolByDottedPath(
+      "[virtual]:operating_system.is_mobile_platform");
+
+  // Assert
+  ASSERT_TRUE(is_mobile_platform);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemName) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
path: components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-05-24T00:03:34Z
author_association: COLLABORATOR
diff_hunk: @@ -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.h"
+#include "base/version_info/version_info.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 prefs = provider().GetVirtualPrefs();
+  const std::string* build_channel =
+      prefs.FindStringByDottedPath("[virtual]:browser.build_channel");
+
+  // Assert
+  ASSERT_TRUE(build_channel);
+  EXPECT_EQ(*build_channel, "release");
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* version =
+      prefs.FindStringByDottedPath("[virtual]:browser.version");
+
+  // Assert
+  ASSERT_TRUE(version);
+  EXPECT_EQ(*version, version_info::GetVersionNumber());
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMajorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> major_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.major_version");
+
+  // Assert
+  EXPECT_EQ(major_version, version_info::GetVersion().components()[0]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserMinorVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> minor_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.minor_version");
+
+  // Assert
+  EXPECT_EQ(minor_version, version_info::GetVersion().components()[1]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserBuildVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> build_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.build_version");
+
+  // Assert
+  EXPECT_EQ(build_version, version_info::GetVersion().components()[2]);
+}
+
+TEST_F(VirtualPrefProviderTest, BrowserPatchVersion) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<int> patch_version =
+      prefs.FindIntByDottedPath("[virtual]:browser.patch_version");
+
+  // Assert
+  EXPECT_EQ(patch_version, version_info::GetVersion().components()[3]);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleLanguage) {
+  // Arrange
+  const test::ScopedCurrentLanguageCode scoped_current_language_code{"en"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* language = prefs.FindStringByDottedPath(
+      "[virtual]:operating_system.locale.language");
+
+  // Assert
+  ASSERT_TRUE(language);
+  EXPECT_EQ(*language, "en");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemLocaleRegion) {
+  // Arrange
+  const test::ScopedCurrentCountryCode scoped_current_country_code{"KY"};
+
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* region =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.locale.region");
+
+  // Assert
+  ASSERT_TRUE(region);
+  EXPECT_EQ(*region, "KY");
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemIsMobilePlatform) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  std::optional<bool> is_mobile_platform = prefs.FindBoolByDottedPath(
+      "[virtual]:operating_system.is_mobile_platform");
+
+  // Assert
+  ASSERT_TRUE(is_mobile_platform);
+}
+
+TEST_F(VirtualPrefProviderTest, OperatingSystemName) {
+  // Act
+  const base::Value::Dict prefs = provider().GetVirtualPrefs();
+  const std::string* name =
+      prefs.FindStringByDottedPath("[virtual]:operating_system.name");
+
+  // Assert
+  ASSERT_TRUE(name);
path: components/brave_ads/core/browser/service/virtual_pref_provider_unittest.cc
user: login: aseren
body: Fixed
created_at: 2025-05-24T00:03:38Z
author_association: COLLABORATOR
diff_hunk: @@ -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_->GetVirtualPrefs());
path: components/brave_ads/browser/ads_service_impl.cc
user: login: aseren
body: Fixed
created_at: 2025-05-24T00:03:42Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,186 @@
+/* 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 <cstddef>
+#include <utility>
+#include <vector>
+
+#include "base/check.h"
+#include "base/json/json_reader.h"
+#include "base/version.h"
+#include "base/version_info/version_info.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";
+
+int GetVersionComponent(size_t index) {
path: components/brave_ads/core/browser/service/virtual_pref_provider.cc
user: login: aseren
body: Fixed
created_at: 2025-05-24T00:03:46Z
author_association: COLLABORATOR
diff_hunk: @@ -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 =
path: ios/browser/brave_ads/virtual_pref_provider_delegate_ios.mm
user: login: aseren
body: There were differences between these iOS and Desktop default search engine due to the following iOS bug, which is fixed now:
* https://github.com/brave/brave-core/pull/29211

This means that for `AU` users who installed Brave version older then v1.81.10 (doesn't contain the fix above), condition matcher with `[virtual]:search_engine|default_name` (which is `Brave Search`) will not match their default engine (which is `Google`).
New users who installed version v1.81.10 or higher won't have such an issue. 
created_at: 2025-05-26T14:41:50Z
author_association: COLLABORATOR
diff_hunk: @@ -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();
path: components/brave_ads/core/browser/service/virtual_pref_provider_util.h
user: login: aseren
body: make them together
created_at: 2025-05-26T15:42:29Z
author_association: COLLABORATOR

PR comments:
user: login: tmancey
created_at: 2025-05-22T12:23:17Z
body: As discussed please use the changes from 1.80 as brave_l10n is deprecated. You may need iOS implementations for some of the iOS prefs or some may not be possible (please document in our SmartNTT Google Doc). Code duplication is fine to get this over the line with a follow up when we continue to unify the ads service.
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-05-24T00:12:44Z
body: [[puLL-Merge](https://github.com/brave/pull-merge)] - [brave/brave-core@29161](https://github.com/brave/brave-core/pull/29161)



# Description
This PR refactors the virtual preferences functionality in Brave's ads service by extracting it from `AdsServiceDelegate` into a dedicated `VirtualPrefProvider` class. The change improves code organization, testability, and platform consistency by consolidating related functionality into a single component.

<details>
<summary><i>Changes</i></summary>

### Changes
1. **browser/brave_ads/virtual_pref_provider_delegate.h/cc**:
   - New files that implement a platform-specific delegate for the virtual preference provider

2. **components/brave_ads/core/browser/service/virtual_pref_provider.h/cc**:
   - New core implementation of the virtual preference provider
   - Handles structuring browser, OS, search engine, and SKU (orders) information

3. **components/brave_ads/core/browser/service/virtual_pref_provider_util.h/cc**:
   - Utility functions for version information and platform detection

4. **browser/brave_ads/ads_service_delegate.cc/h**:
   - Removed methods related to virtual preferences that are now handled by the new class
   - Removed code for SKU handling and browser version reporting

5. **components/brave_ads/browser/ads_service_impl.cc/h**:
   - Updated to use the new VirtualPrefProvider class
   - Added a member variable to hold the provider instance

6. **ios/browser/brave_ads/**:
   - Added iOS-specific implementation of the virtual preference provider delegate

7. **Build files**:
   - Updated to include the new files and dependencies
   - Removed dependency on skus from some components since it's handled in VirtualPrefProvider

8. **Added tests**:
   - Comprehensive unit tests for the new VirtualPrefProvider functionality

```mermaid
sequenceDiagram
    participant Client
    participant AdsServiceImpl
    participant VirtualPrefProvider
    participant VirtualPrefProviderDelegate
    participant PrefService

    Client->>AdsServiceImpl: GetVirtualPrefs()
    AdsServiceImpl->>VirtualPrefProvider: GetPrefs()
    VirtualPrefProvider->>VirtualPrefProviderDelegate: GetChannel()
    VirtualPrefProviderDelegate-->>VirtualPrefProvider: "release"
    VirtualPrefProvider->>VirtualPrefProviderDelegate: GetDefaultSearchEngineName()
    VirtualPrefProviderDelegate-->>VirtualPrefProvider: "Brave"
    VirtualPrefProvider->>PrefService: GetDict(skus::prefs::kSkusState)
    PrefService-->>VirtualPrefProvider: SKUs data
    VirtualPrefProvider-->>AdsServiceImpl: Combined preference dictionary
    AdsServiceImpl-->>Client: Virtual preferences
```
</details>




<!-- Generated by claude-3-7-sonnet-20250219 -->
author_association: CONTRIBUTOR
user: login: brave-builds
created_at: 2025-05-27T00:04:55Z
body: Released in [v1.81.15](https://github.com/brave/brave-browser/releases/tag/v1.81.15)
author_association: COLLABORATOR
user: login: btlechowski
created_at: 2025-06-01T15:04:12Z
body: Verified with

Brave | 1.80.96 Chromium: 137.0.7151.61 (Official Build) beta (64-bit)
-- | --
Revision | aa57feb61462c3fe2ca3a58c9090031a5c6a7bb7
OS | Linux


#### [virtual]:browser|build_channel

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:browser|build_channel",
                            "condition": "beta"
                        }
                    ],
```

Verified ad is shown

</details>

#### [virtual]:operating_system

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:operating_system|name",
                            "condition": "Linux"
                        }
                    ],
```

Verified ad is shown

</details>


#### [virtual]:search_engine

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:search_engine|default_name",
                            "condition": "Brave"
                        }
                    ],
```

Verified ad is shown

</details>

#### [virtual]:skus

<details>

```
                    "conditionMatchers": [{
                            "condition": "[T=]:0",
                            "prefPath": "[virtual]:skus|production|talk.brave.com|created_at"
                        }
                    ],
```

Verified ad is shown

</details>


author_association: NONE
user: login: btlechowski
created_at: 2025-06-01T16:14:21Z
body: Verification passed on Brave v1.80.96 on Lenovo TB-8506FS (Android 11.0)

#### [virtual]:browser|build_channel

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:browser|build_channel",
                            "condition": "beta"
                        }
                    ],
```

Verified ad is shown

</details>

#### [virtual]:operating_system

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:operating_system|name",
                            "condition": "Linux"
                        }
                    ],
```

Verified ad is shown

</details>


#### [virtual]:search_engine

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:search_engine|default_name",
                            "condition": "Brave"
                        }
                    ],
```

Verified ad is shown

</details>

#### [virtual]:skus

<details>

```
                    "conditionMatchers": [{
                            "condition": "[T>]:0",
                            "prefPath": "[virtual]:skus|production|talk.brave.com|created_at"
                        }
                    ],
```

Verified ad is shown

</details>


author_association: NONE
user: login: btlechowski
created_at: 2025-06-09T22:09:29Z
body: Verification passed on 1.81.63 on iPhone 13 Pro Max (iOS 18.5)

#### [virtual]:browser|build_channel

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:browser|build_channel",
                            "condition": "nightly"
                        }
                    ],
```

Verified ad is shown

```
info	22:09:13.554156+0200	Client	[ads] Served new tab page ad impression:
  placementId: 492f3ceb-b532-49b2-82b3-b9ec259983cf
  creativeInstanceId: 143148ee-bd08-41f2-a8e0-fd8516e02975
  creativeSetId: 277a6bf1-3bf7-4d21-a917-04c6e97fb26d
  campaignId: be59cc83-0b4e-49f1-a8c7-53fd7211587f
  advertiserId: 846dfc87-cea8-46f2-98b1-d9e42ef147b6
  segment: untargeted
  companyName: Brave
  alt: [virtual]:browser|build_channel -> nightly
  targetUrl: https://blocksurvey.io/_tuNDvd7RiKIYRXeA6Epjg?v=l
info	22:09:13.557390+0200	Client	[ads] Served new tab page ad impression with placement id 492f3ceb-b532-49b2-82b3-b9ec259983cf and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975

info	22:09:14.071789+0200	Client	[ads] Viewed new tab page ad impression with placement id E5585B66-B8BE-4B2E-B586-319591B41CF4 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
```

</details>

#### [virtual]:operating_system

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:operating_system|name",
                            "condition": "iOS"
                        }
                    ],
```

Verified ad is shown

```
info	23:43:50.534188+0200	Client	[ads] Served new tab page ad impression:
  placementId: 0eb6e9e8-7cde-4e0d-85a0-2ec06763e43d
  creativeInstanceId: 143148ee-bd08-41f2-a8e0-fd8516e02975
  creativeSetId: 277a6bf1-3bf7-4d21-a917-04c6e97fb26d
  campaignId: be59cc83-0b4e-49f1-a8c7-53fd7211587f
  advertiserId: 846dfc87-cea8-46f2-98b1-d9e42ef147b6
  segment: untargeted
  companyName: Brave
  alt: [virtual]:operating_system|name -> iOS
  targetUrl: https://blocksurvey.io/_tuNDvd7RiKIYRXeA6Epjg?v=l
info	23:43:50.536737+0200	Client	[ads] Served new tab page ad impression with placement id 0eb6e9e8-7cde-4e0d-85a0-2ec06763e43d and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	23:43:51.107278+0200	Client	[ads] Viewed new tab page ad impression with placement id DD7DEBDF-B4C8-4EC9-97A3-D2C5CF510EB1 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	23:43:51.107786+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 and kViewedImpression valued at 0
info	23:43:51.107850+0200	Client	[ads] Confirming kViewedImpression for kNewTabPageAd with transaction id 225c780e-8df7-4dfc-a94f-9138e6818ebe and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	23:43:51.109668+0200	Client	[ads] Successfully added kViewedImpression confirmation for kNewTabPageAd with transaction id 225c780e-8df7-4dfc-a94f-9138e6818ebe and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 to the queue
info	23:43:51.110317+0200	Client	[ads] Process kViewedImpression confirmation for kNewTabPageAd with transaction id 225c780e-8df7-4dfc-a94f-9138e6818ebe and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 in 0 hours, 0 minutes, 5 seconds at 23:43:56.109
info	23:43:56.111601+0200	Client	[ads] Redeem non-reward confirmation
info	23:43:56.111810+0200	Client	[ads] Create non-reward confirmation
info	23:43:56.112132+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v4/confirmation/225c780e-8df7-4dfc-a94f-9138e6818ebe
  Content: {"creativeInstanceId":"143148ee-bd08-41f2-a8e0-fd8516e02975","type":"view"}
  Content Type: application/json
  Method: kPost
info	23:43:56.112288+0200	Client	[ads]   Headers:
    accept: application/json
info	23:43:57.188308+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v4/confirmation/225c780e-8df7-4dfc-a94f-9138e6818ebe
  Response Status Code: 204
  Response:
info	23:43:57.188627+0200	Client	[ads]   Headers:
    Accept-Ranges: bytes
    Date: Mon, 09 Jun 2025 21:43:57 GMT
    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
    Vary: Origin
    Via: 1.1 varnish
    access-control-allow-credentials: true
    content-security-policy: sandbox
    cross-origin-opener-policy: same-origin
    cross-origin-resource-policy: same-origin
    referrer-policy: same-origin
    x-cache: MISS
    x-cache-hits: 0
    x-content-type-options: nosniff
    x-frame-options: DENY
    x-served-by: cache-hel1410034-HEL
info	23:43:57.188713+0200	Client	[ads] Successfully redeemed non-reward kViewedImpression confirmation for kNewTabPageAd with transaction id 225c780e-8df7-4dfc-a94f-9138e6818ebe and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	23:43:57.193849+0200	Client	[ads] Successfully processed kViewedImpression confirmation for kNewTabPageAd with transaction id 225c780e-8df7-4dfc-a94f-9138e6818ebe and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
```

</details>


#### [virtual]:search_engine

<details>

```
                    "conditionMatchers": [{
                            "prefPath": "[virtual]:search_engine|default_name",
                            "condition": "Google"
                        }
                    ],
```

Verified ad is shown

```
info	15:52:24.670578+0200	Client	[ads] Served new tab page ad impression:
  placementId: da78ff41-98ea-45d7-b215-efa6ecf1537d
  creativeInstanceId: 143148ee-bd08-41f2-a8e0-fd8516e02975
  creativeSetId: 277a6bf1-3bf7-4d21-a917-04c6e97fb26d
  campaignId: be59cc83-0b4e-49f1-a8c7-53fd7211587f
  advertiserId: 846dfc87-cea8-46f2-98b1-d9e42ef147b6
  segment: untargeted
  companyName: Brave
  alt: [virtual]:search_engine|default_name -> Google
  targetUrl: https://blocksurvey.io/_tuNDvd7RiKIYRXeA6Epjg?v=l
info	15:52:24.684348+0200	Client	[ads] Served new tab page ad impression with placement id da78ff41-98ea-45d7-b215-efa6ecf1537d and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975

info	15:52:25.174776+0200	Client	[ads] Viewed new tab page ad impression with placement id A6B35030-6E80-4985-AC22-57D1F0AB291C and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	15:52:25.181251+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 and kViewedImpression valued at 0
info	15:52:25.181322+0200	Client	[ads] Confirming kViewedImpression for kNewTabPageAd with transaction id b171e089-32d4-4713-8de0-fc5eb9d8f18b and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	15:52:25.183793+0200	Client	[ads] Successfully added kViewedImpression confirmation for kNewTabPageAd with transaction id b171e089-32d4-4713-8de0-fc5eb9d8f18b and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 to the queue
info	15:52:25.189433+0200	Client	[ads] Process kViewedImpression confirmation for kNewTabPageAd with transaction id b171e089-32d4-4713-8de0-fc5eb9d8f18b and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 in 0 hours, 0 minutes, 5 seconds at 15:52:30.183
info	15:52:30.184684+0200	Client	[ads] Redeem non-reward confirmation
info	15:52:30.184772+0200	Client	[ads] Create non-reward confirmation
info	15:52:30.184961+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v4/confirmation/b171e089-32d4-4713-8de0-fc5eb9d8f18b
  Content: {"creativeInstanceId":"143148ee-bd08-41f2-a8e0-fd8516e02975","type":"view"}
  Content Type: application/json
  Method: kPost
info	15:52:30.185039+0200	Client	[ads]   Headers:
    accept: application/json
info	15:52:31.142155+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v4/confirmation/b171e089-32d4-4713-8de0-fc5eb9d8f18b
  Response Status Code: 204
  Response:
info	15:52:31.142343+0200	Client	[ads]   Headers:
    Accept-Ranges: bytes
    Date: Tue, 10 Jun 2025 13:52:31 GMT
    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
    Vary: Origin
    Via: 1.1 varnish
    access-control-allow-credentials: true
    content-security-policy: sandbox
    cross-origin-opener-policy: same-origin
    cross-origin-resource-policy: same-origin
    referrer-policy: same-origin
    x-cache: MISS
    x-cache-hits: 0
    x-content-type-options: nosniff
    x-frame-options: DENY
    x-served-by: cache-hel1410021-HEL
info	15:52:31.142385+0200	Client	[ads] Successfully redeemed non-reward kViewedImpression confirmation for kNewTabPageAd with transaction id b171e089-32d4-4713-8de0-fc5eb9d8f18b and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	15:52:31.146742+0200	Client	[ads] Successfully processed kViewedImpression confirmation for kNewTabPageAd with transaction id b171e089-32d4-4713-8de0-fc5eb9d8f18b and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
```

</details>

#### [virtual]:skus

<details>

```
                    "conditionMatchers": [{
                            "condition": "[T>]:0",
                            "prefPath": "[virtual]:skus|production|talk.brave.com|created_at"
                        }
                    ],
```

Verified ad is shown

```
info	17:16:34.973022+0200	Client	[ads] Served new tab page ad impression:
  placementId: 0badcbcc-57b1-46dc-9d9c-4e515041a9a0
  creativeInstanceId: 143148ee-bd08-41f2-a8e0-fd8516e02975
  creativeSetId: 277a6bf1-3bf7-4d21-a917-04c6e97fb26d
  campaignId: be59cc83-0b4e-49f1-a8c7-53fd7211587f
  advertiserId: 846dfc87-cea8-46f2-98b1-d9e42ef147b6
  segment: untargeted
  companyName: Brave
  alt: [virtual]:skus|production|talk.brave.com|created_at -> [T>]:0
  targetUrl: https://blocksurvey.io/_tuNDvd7RiKIYRXeA6Epjg?v=l
info	17:16:34.976552+0200	Client	[ads] Served new tab page ad impression with placement id 0badcbcc-57b1-46dc-9d9c-4e515041a9a0 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	17:16:35.563900+0200	Client	[ads] Served new tab page ad impression with placement id A541C6AA-121C-4630-BDFA-E56D12E7CC9E and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	17:16:35.566803+0200	Client	[ads] Viewed new tab page ad impression with placement id A541C6AA-121C-4630-BDFA-E56D12E7CC9E and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	17:16:35.569813+0200	Client	[ads] Successfully processed deposit for kNewTabPageAd with creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 and kViewedImpression valued at 0
info	17:16:35.569839+0200	Client	[ads] Confirming kViewedImpression for kNewTabPageAd with transaction id 933b07dd-81a5-41a3-aef6-1b6d24798266 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	17:16:35.571849+0200	Client	[ads] Successfully added kViewedImpression confirmation for kNewTabPageAd with transaction id 933b07dd-81a5-41a3-aef6-1b6d24798266 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 to the queue
info	17:16:35.572644+0200	Client	[ads] Process kViewedImpression confirmation for kNewTabPageAd with transaction id 933b07dd-81a5-41a3-aef6-1b6d24798266 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975 in 0 hours, 0 minutes, 5 seconds at 17:16:40.571
info	17:16:40.573331+0200	Client	[ads] Redeem non-reward confirmation
info	17:16:40.573430+0200	Client	[ads] Create non-reward confirmation
info	17:16:40.573728+0200	Client	[ads] URL Request:
  URL: https://anonymous.ads.bravesoftware.com/v4/confirmation/933b07dd-81a5-41a3-aef6-1b6d24798266
  Content: {"creativeInstanceId":"143148ee-bd08-41f2-a8e0-fd8516e02975","type":"view"}
  Content Type: application/json
  Method: kPost
info	17:16:40.573826+0200	Client	[ads]   Headers:
    accept: application/json
info	17:16:41.260750+0200	Client	[ads] URL Response:
  URL: https://anonymous.ads.bravesoftware.com/v4/confirmation/933b07dd-81a5-41a3-aef6-1b6d24798266
  Response Status Code: 204
  Response:
info	17:16:41.261044+0200	Client	[ads]   Headers:
    Accept-Ranges: bytes
    Date: Tue, 10 Jun 2025 15:16:41 GMT
    Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
    Vary: Origin
    Via: 1.1 varnish
    access-control-allow-credentials: true
    content-security-policy: sandbox
    cross-origin-opener-policy: same-origin
    cross-origin-resource-policy: same-origin
    referrer-policy: same-origin
    x-cache: MISS
    x-cache-hits: 0
    x-content-type-options: nosniff
    x-frame-options: DENY
    x-served-by: cache-hel1410034-HEL
info	17:16:41.261236+0200	Client	[ads] Successfully redeemed non-reward kViewedImpression confirmation for kNewTabPageAd with transaction id 933b07dd-81a5-41a3-aef6-1b6d24798266 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
info	17:16:41.268778+0200	Client	[ads] Successfully processed kViewedImpression confirmation for kNewTabPageAd with transaction id 933b07dd-81a5-41a3-aef6-1b6d24798266 and creative instance id 143148ee-bd08-41f2-a8e0-fd8516e02975
```

</details>


author_association: NONE