[ads] Add [virtual]:feature=<name> condition matcher virtual pref (#35896)
Adds `[virtual]:feature=<feature_name>|is_overridden` and `[virtual]:feature=<feature_name>|params|<param_name>` virtual pref paths so campaign authors can check whether a named feature flag has been explicitly overridden via Griffin, a command-line switch, or a compile-time default, and inspect its field trial parameter values. Any feature name is supported without a hardcoded list.
This commit is contained in:
@@ -20,6 +20,7 @@ A place to define all specific terms and vocabulary for the Brave Ads component,
|
||||
| Catalog | A collection of available campaigns, creative sets and creative instances. |
|
||||
| Click | Refers to a user interacting with an advertisement by clicking on the ad. |
|
||||
| Click-through rate | The percentage of ad impressions that result in clicks. |
|
||||
| Condition matcher | One or more pref path and condition pairs, evaluated with AND logic on the user's device, that must all match before a new tab page ad can be served. Paths may reference stored prefs or virtual prefs computed at runtime, and conditions may use epoch, numerical, regex, or pattern operators. |
|
||||
| Confirmations | Confirm events, i.e., views, without revealing to Brave the particular user involved. See [security and privacy model for ad confirmations](https://github.com/brave/brave-browser/wiki/Security-and-privacy-model-for-ad-confirmations). |
|
||||
| Contextual | Contextual advertising targets ads based on the web page's content or the user's online activity context to deliver relevant and personalized advertisements. |
|
||||
| Conversion | When a user triggers an action, it is counted as a conversion. Conversions include making a purchase or signing up for a newsletter. |
|
||||
|
||||
@@ -874,6 +874,8 @@ static_library("internal") {
|
||||
"serving/targeting/condition_matcher/matchers/regex_condition_matcher_util.h",
|
||||
"serving/targeting/condition_matcher/prefs/condition_matcher_pref_util.cc",
|
||||
"serving/targeting/condition_matcher/prefs/condition_matcher_pref_util.h",
|
||||
"serving/targeting/condition_matcher/prefs/internal/condition_matcher_feature_pref_util_internal.cc",
|
||||
"serving/targeting/condition_matcher/prefs/internal/condition_matcher_feature_pref_util_internal.h",
|
||||
"serving/targeting/condition_matcher/prefs/internal/condition_matcher_keyword_path_component_util.cc",
|
||||
"serving/targeting/condition_matcher/prefs/internal/condition_matcher_keyword_path_component_util.h",
|
||||
"serving/targeting/condition_matcher/prefs/internal/condition_matcher_pref_util_internal.cc",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/time/time.h"
|
||||
#include "brave/components/brave_ads/core/internal/ads_client/ads_client_util.h"
|
||||
#include "brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_feature_pref_util_internal.h"
|
||||
#include "brave/components/brave_ads/core/public/ads_client/ads_client.h"
|
||||
|
||||
namespace brave_ads {
|
||||
@@ -390,6 +391,10 @@ bool HasLocalStatePrefPath(const std::string& path) {
|
||||
std::optional<base::Value> GetVirtualPref(const base::DictValue& virtual_prefs,
|
||||
std::string_view path) {
|
||||
if (path.starts_with(kVirtualPrefPathPrefix)) {
|
||||
if (std::optional<base::Value> dict = MaybeGetFeaturePrefValue(path)) {
|
||||
return dict;
|
||||
}
|
||||
|
||||
if (const base::Value* const value = virtual_prefs.Find(path)) {
|
||||
return value->Clone();
|
||||
}
|
||||
|
||||
+32
@@ -239,6 +239,38 @@ class DictValue;
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// 9. "[virtual]:feature=<feature_name>|is_overridden"
|
||||
// - Returns "1" if the named feature flag has been explicitly overridden
|
||||
// (via Griffin, a command-line switch, or a compile-time default
|
||||
// override), or "0" otherwise. Any feature name is supported, including
|
||||
// future ones not yet known at build time. For example, the following
|
||||
// condition matcher will match when `NotificationAdFeature` is
|
||||
// overridden:
|
||||
//
|
||||
// "conditionMatchers": [
|
||||
// {
|
||||
// "condition": "1",
|
||||
// "prefPath":
|
||||
// "[virtual]:feature=NotificationAdFeature|is_overridden"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// 10. "[virtual]:feature=<feature_name>|params|<param_name>"
|
||||
// - Returns the string value of the named field trial param for the given
|
||||
// feature, or does not match if the feature has no associated trial or
|
||||
// the param does not exist. Combine with numerical operators to compare
|
||||
// numeric param values. For example, the following condition matcher
|
||||
// will match when the `version` param for `NotificationAdServing`
|
||||
// equals 2:
|
||||
//
|
||||
// "conditionMatchers": [
|
||||
// {
|
||||
// "condition": "[R=]:2",
|
||||
// "prefPath":
|
||||
// "[virtual]:feature=NotificationAdServing|params|version"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// NOTE: To identify condition matchers, first create a copy of your
|
||||
// brave://local-state and `Default/Preferences` files. Next, change a
|
||||
// brave://setting or enable a feature, quit the browser and then compare the
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_feature_pref_util_internal.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/metrics/field_trial.h"
|
||||
#include "base/metrics/field_trial_params.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_keyword_path_component_util.h"
|
||||
|
||||
namespace brave_ads {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr std::string_view kIsOverriddenKey = "is_overridden";
|
||||
constexpr std::string_view kParamsKey = "params";
|
||||
|
||||
} // namespace
|
||||
|
||||
std::optional<base::Value> MaybeGetFeaturePrefValue(
|
||||
std::string_view path_component) {
|
||||
std::optional<std::string_view> feature_name =
|
||||
MaybeParseKeywordPathComponentValue(path_component,
|
||||
kFeatureVirtualPrefKeyword);
|
||||
if (!feature_name || feature_name->empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
base::FeatureList* const feature_list = base::FeatureList::GetInstance();
|
||||
|
||||
const bool is_overridden = feature_list != nullptr &&
|
||||
feature_list->IsFeatureOverridden(*feature_name);
|
||||
|
||||
base::DictValue dict = base::DictValue().Set(
|
||||
kIsOverriddenKey, base::NumberToString(static_cast<int>(is_overridden)));
|
||||
|
||||
if (feature_list) {
|
||||
const base::FieldTrial* const field_trial =
|
||||
feature_list->GetAssociatedFieldTrialByFeatureName(*feature_name);
|
||||
if (field_trial != nullptr) {
|
||||
base::FieldTrialParams field_trial_params;
|
||||
if (base::GetFieldTrialParams(field_trial->trial_name(),
|
||||
&field_trial_params)) {
|
||||
base::DictValue params_dict;
|
||||
for (const auto& [param_name, param_value] : field_trial_params) {
|
||||
params_dict.Set(param_name, param_value);
|
||||
}
|
||||
dict.Set(kParamsKey, std::move(params_dict));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base::Value(std::move(dict));
|
||||
}
|
||||
|
||||
} // namespace brave_ads
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_SERVING_TARGETING_CONDITION_MATCHER_PREFS_INTERNAL_CONDITION_MATCHER_FEATURE_PREF_UTIL_INTERNAL_H_
|
||||
#define BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_SERVING_TARGETING_CONDITION_MATCHER_PREFS_INTERNAL_CONDITION_MATCHER_FEATURE_PREF_UTIL_INTERNAL_H_
|
||||
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
namespace base {
|
||||
class Value;
|
||||
} // namespace base
|
||||
|
||||
// Resolves `[virtual]:feature=<feature_name>` keyword path components into a
|
||||
// dict whose sub-paths are traversed via the normal `|` separator. Any feature
|
||||
// name is supported, including future ones not yet known at build time.
|
||||
|
||||
namespace brave_ads {
|
||||
|
||||
inline constexpr std::string_view kFeatureVirtualPrefKeyword =
|
||||
"[virtual]:feature";
|
||||
|
||||
// Returns a dict with an `is_overridden` string, either "1" if overridden or
|
||||
// "0" if not; unknown feature names are treated as not overridden. When a
|
||||
// field trial is associated the dict also contains a `params` sub-dict of
|
||||
// param name-value pairs; otherwise `params` is absent and any
|
||||
// `|params|<param_name>` traversal returns nothing. Returns `std::nullopt` if
|
||||
// `path_component` does not match `[virtual]:feature=<feature_name>` or the
|
||||
// feature name is empty.
|
||||
std::optional<base::Value> MaybeGetFeaturePrefValue(
|
||||
std::string_view path_component);
|
||||
|
||||
} // namespace brave_ads
|
||||
|
||||
#endif // BRAVE_COMPONENTS_BRAVE_ADS_CORE_INTERNAL_SERVING_TARGETING_CONDITION_MATCHER_PREFS_INTERNAL_CONDITION_MATCHER_FEATURE_PREF_UTIL_INTERNAL_H_
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/* Copyright (c) 2026 The Brave Authors. All rights reserved.
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_feature_pref_util_internal.h"
|
||||
|
||||
#include "base/feature_list.h"
|
||||
#include "base/test/scoped_feature_list.h"
|
||||
#include "base/values.h"
|
||||
#include "brave/components/brave_ads/core/internal/common/test/test_base.h"
|
||||
|
||||
// npm run test -- brave_all_unit_tests --filter=BraveAds*
|
||||
|
||||
namespace brave_ads {
|
||||
|
||||
namespace {
|
||||
BASE_FEATURE(kTestFeature, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
} // namespace
|
||||
|
||||
class BraveAdsConditionMatcherFeaturePrefUtilInternalTest
|
||||
: public test::TestBase {};
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
GetIsOverriddenWhenFeatureIsOverridden) {
|
||||
// Arrange
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(kTestFeature);
|
||||
|
||||
// Act
|
||||
std::optional<base::Value> feature_dict =
|
||||
MaybeGetFeaturePrefValue("[virtual]:feature=TestFeature");
|
||||
|
||||
// Assert
|
||||
ASSERT_TRUE(feature_dict);
|
||||
EXPECT_EQ("1", *feature_dict->GetDict().FindString("is_overridden"));
|
||||
}
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
GetIsNotOverriddenWhenFeatureIsNotOverridden) {
|
||||
// Act
|
||||
std::optional<base::Value> feature_dict =
|
||||
MaybeGetFeaturePrefValue("[virtual]:feature=TestFeature");
|
||||
|
||||
// Assert
|
||||
ASSERT_TRUE(feature_dict);
|
||||
EXPECT_EQ("0", *feature_dict->GetDict().FindString("is_overridden"));
|
||||
}
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
GetParamsWhenFeatureHasTrial) {
|
||||
// Arrange
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeatureWithParameters(kTestFeature,
|
||||
{{"foo", "bar"}});
|
||||
|
||||
// Act
|
||||
std::optional<base::Value> feature_dict =
|
||||
MaybeGetFeaturePrefValue("[virtual]:feature=TestFeature");
|
||||
|
||||
// Assert
|
||||
ASSERT_TRUE(feature_dict);
|
||||
const auto* const params_dict = feature_dict->GetDict().FindDict("params");
|
||||
ASSERT_TRUE(params_dict);
|
||||
EXPECT_EQ("bar", *params_dict->FindString("foo"));
|
||||
}
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
DoNotGetParamsDictWhenFeatureHasNoTrial) {
|
||||
// Arrange
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndEnableFeature(kTestFeature);
|
||||
|
||||
// Act
|
||||
std::optional<base::Value> feature_dict =
|
||||
MaybeGetFeaturePrefValue("[virtual]:feature=TestFeature");
|
||||
|
||||
// Assert
|
||||
ASSERT_TRUE(feature_dict);
|
||||
EXPECT_FALSE(feature_dict->GetDict().FindDict("params"));
|
||||
}
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
DoNotGetFeaturePrefValueForNonKeywordPath) {
|
||||
// Act & Assert
|
||||
EXPECT_FALSE(MaybeGetFeaturePrefValue("TestFeature"));
|
||||
}
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
DoNotGetFeaturePrefValueForEmptyFeatureName) {
|
||||
// Act & Assert
|
||||
EXPECT_FALSE(MaybeGetFeaturePrefValue("[virtual]:feature="));
|
||||
}
|
||||
|
||||
TEST_F(BraveAdsConditionMatcherFeaturePrefUtilInternalTest,
|
||||
DoNotGetFeaturePrefValueForKeywordWithNoEqualsSign) {
|
||||
// Act & Assert
|
||||
EXPECT_FALSE(MaybeGetFeaturePrefValue("[virtual]:feature"));
|
||||
}
|
||||
|
||||
} // namespace brave_ads
|
||||
@@ -530,6 +530,7 @@ source_set("brave_ads_unit_tests") {
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/matchers/pattern_condition_matcher_util_unittest.cc",
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/matchers/regex_condition_matcher_util_unittest.cc",
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/condition_matcher_pref_util_unittest.cc",
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_feature_pref_util_internal_unittest.cc",
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_keyword_path_component_util_unittest.cc",
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_pref_util_internal_unittest.cc",
|
||||
"//brave/components/brave_ads/core/internal/serving/targeting/condition_matcher/prefs/internal/condition_matcher_time_period_storage_pref_util_internal_unittest.cc",
|
||||
|
||||
Reference in New Issue
Block a user