Commit c3e9aea7 authored by Elly Fong-Jones's avatar Elly Fong-Jones Committed by Commit Bot

flags: treat expired flags as default state

Currently, when a flag expires, it is hidden from chrome://flags but still
stored inside the backend flags store, so that if the user un-expires flags
their settings are not lost. However, the code for applying flag values by
turning them into features/switches was not aware of flag expiration, so flags
that were persisted this way would continue to apply, despite the user having no
way to undo them.

This change:
1) Adds an exclusion predicate to FlagsState, to allow client classes of
   FlagsState to prevent flags from having their values applied as features or
   switches;
2) Adds an implementation of that predicate in the //chrome FlagsStateSingleton
   that checks the flag expiration state;
3) Adds unit tests to cover the new behavior added in (1);
4) Adds a new AboutFlagsBrowserTest test to validate the end-to-end behavior
   described in this commit message by:
    a) Setting an expired flag to a non-default value
    b) Restarting (simulated via separate PRE_ vs regular tests here)
    c) Checking that the flag's switch isn't in the browser command line

TBR=rohitrao@chromium.org

Bug: 1009729
Change-Id: I7d0ee2e7660fa378b9ecec3b663cc794f7e776fd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1879431Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Reviewed-by: default avatarAlexei Svitkine <asvitkine@chromium.org>
Commit-Queue: Elly Fong-Jones <ellyjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#709169}
parent 3157471e
...@@ -4725,9 +4725,14 @@ class FlagsStateSingleton { ...@@ -4725,9 +4725,14 @@ class FlagsStateSingleton {
FlagsStateSingleton() FlagsStateSingleton()
: flags_state_(std::make_unique<flags_ui::FlagsState>( : flags_state_(std::make_unique<flags_ui::FlagsState>(
kFeatureEntries, kFeatureEntries,
base::size(kFeatureEntries))) {} base::size(kFeatureEntries),
base::Bind(&FlagsStateSingleton::IsFlagExpired))) {}
~FlagsStateSingleton() {} ~FlagsStateSingleton() {}
static bool IsFlagExpired(const flags_ui::FeatureEntry& entry) {
return flags::IsFlagExpired(entry.internal_name);
}
static FlagsStateSingleton* GetInstance() { static FlagsStateSingleton* GetInstance() {
return base::Singleton<FlagsStateSingleton>::get(); return base::Singleton<FlagsStateSingleton>::get();
} }
...@@ -4737,8 +4742,9 @@ class FlagsStateSingleton { ...@@ -4737,8 +4742,9 @@ class FlagsStateSingleton {
} }
void RebuildState(const std::vector<flags_ui::FeatureEntry>& entries) { void RebuildState(const std::vector<flags_ui::FeatureEntry>& entries) {
flags_state_ = flags_state_ = std::make_unique<flags_ui::FlagsState>(
std::make_unique<flags_ui::FlagsState>(entries.data(), entries.size()); entries.data(), entries.size(),
base::Bind(&FlagsStateSingleton::IsFlagExpired));
} }
private: private:
......
...@@ -315,4 +315,28 @@ IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, ExpiryHidesFlag) { ...@@ -315,4 +315,28 @@ IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, ExpiryHidesFlag) {
EXPECT_TRUE(IsFlagPresent(contents, kExpiredFlagName)); EXPECT_TRUE(IsFlagPresent(contents, kExpiredFlagName));
} }
#if !defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, PRE_ExpiredFlagDoesntApply) {
set_expiration_enabled(false);
NavigateToFlagsPage();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(IsFlagPresent(contents, kExpiredFlagName));
EXPECT_FALSE(IsDropdownEnabled(contents, kExpiredFlagName));
ToggleEnableDropdown(contents, kExpiredFlagName, true);
}
IN_PROC_BROWSER_TEST_P(AboutFlagsBrowserTest, ExpiredFlagDoesntApply) {
set_expiration_enabled(true);
NavigateToFlagsPage();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(IsFlagPresent(contents, kExpiredFlagName));
EXPECT_FALSE(base::CommandLine::ForCurrentProcess()->HasSwitch(
kExpiredFlagSwitchName));
}
#endif
} // namespace } // namespace
...@@ -119,7 +119,7 @@ void AddOsStrings(unsigned bitmask, base::ListValue* list) { ...@@ -119,7 +119,7 @@ void AddOsStrings(unsigned bitmask, base::ListValue* list) {
// Confirms that an entry is valid, used in a DCHECK in // Confirms that an entry is valid, used in a DCHECK in
// SanitizeList below. // SanitizeList below.
bool ValidateFeatureEntry(const FeatureEntry& e) { bool IsValidFeatureEntry(const FeatureEntry& e) {
switch (e.type) { switch (e.type) {
case FeatureEntry::SINGLE_VALUE: case FeatureEntry::SINGLE_VALUE:
case FeatureEntry::SINGLE_DISABLE_VALUE: case FeatureEntry::SINGLE_DISABLE_VALUE:
...@@ -348,11 +348,14 @@ struct FlagsState::SwitchEntry { ...@@ -348,11 +348,14 @@ struct FlagsState::SwitchEntry {
SwitchEntry() : feature_state(false) {} SwitchEntry() : feature_state(false) {}
}; };
FlagsState::FlagsState(const FeatureEntry* feature_entries, FlagsState::FlagsState(
size_t num_feature_entries) const FeatureEntry* feature_entries,
size_t num_feature_entries,
base::RepeatingCallback<bool(const FeatureEntry&)> exclude_predicate)
: feature_entries_(feature_entries), : feature_entries_(feature_entries),
num_feature_entries_(num_feature_entries), num_feature_entries_(num_feature_entries),
needs_restart_(false) {} needs_restart_(false),
exclude_predicate_(exclude_predicate) {}
FlagsState::~FlagsState() {} FlagsState::~FlagsState() {}
...@@ -836,17 +839,9 @@ std::set<std::string> FlagsState::SanitizeList( ...@@ -836,17 +839,9 @@ std::set<std::string> FlagsState::SanitizeList(
// an O(n^2) search, this is more efficient than creating a set from // an O(n^2) search, this is more efficient than creating a set from
// |feature_entries_| first because |feature_entries_| is large and // |feature_entries_| first because |feature_entries_| is large and
// |enabled_entries| should generally be small/empty. // |enabled_entries| should generally be small/empty.
const FeatureEntry* features_end = feature_entries_ + num_feature_entries_;
for (const std::string& entry_name : enabled_entries) { for (const std::string& entry_name : enabled_entries) {
if (features_end != if (IsSupportedFeature(entry_name, platform_mask))
std::find_if(feature_entries_, features_end,
[entry_name, platform_mask](const FeatureEntry& e) {
DCHECK(ValidateFeatureEntry(e));
return (e.supported_platforms & platform_mask) &&
e.InternalNameMatches(entry_name);
})) {
new_enabled_entries.insert(entry_name); new_enabled_entries.insert(entry_name);
}
} }
return new_enabled_entries; return new_enabled_entries;
...@@ -948,4 +943,21 @@ const FeatureEntry* FlagsState::FindFeatureEntryByName( ...@@ -948,4 +943,21 @@ const FeatureEntry* FlagsState::FindFeatureEntryByName(
return nullptr; return nullptr;
} }
bool FlagsState::IsSupportedFeature(const std::string& name,
int platform_mask) const {
base::span<const FeatureEntry> features(feature_entries_,
num_feature_entries_);
for (const auto& e : features) {
DCHECK(IsValidFeatureEntry(e));
if (!(e.supported_platforms & platform_mask))
continue;
if (!e.InternalNameMatches(name))
continue;
if (exclude_predicate_ && exclude_predicate_.Run(e))
continue;
return true;
}
return false;
}
} // namespace flags_ui } // namespace flags_ui
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/callback_forward.h" #include "base/callback.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -60,7 +60,13 @@ enum FlagAccess { kGeneralAccessFlagsOnly, kOwnerAccessToFlags }; ...@@ -60,7 +60,13 @@ enum FlagAccess { kGeneralAccessFlagsOnly, kOwnerAccessToFlags };
// Stores and encapsulates the little state that about:flags has. // Stores and encapsulates the little state that about:flags has.
class FlagsState { class FlagsState {
public: public:
FlagsState(const FeatureEntry* feature_entries, size_t num_feature_entries); // The |exclude_predicate| parameter is a predicate used to prevent flags from
// actually applying, while retaining them in the list of feature entries.
// This is used to implement flag expiration.
FlagsState(
const FeatureEntry* feature_entries,
size_t num_feature_entries,
base::RepeatingCallback<bool(const FeatureEntry&)> exclude_predicate);
~FlagsState(); ~FlagsState();
// Reads the state from |flags_storage| and adds the command line flags // Reads the state from |flags_storage| and adds the command line flags
...@@ -210,6 +216,13 @@ class FlagsState { ...@@ -210,6 +216,13 @@ class FlagsState {
const FeatureEntry* FindFeatureEntryByName( const FeatureEntry* FindFeatureEntryByName(
const std::string& internal_name) const; const std::string& internal_name) const;
// Returns whether there is a FeatureEntry named by |name| in
// |feature_entries_| that:
// a) Is supported on this |platform_mask|, and
// b) Is not excluded by |exclude_predicate_|, if it is set (i.e. for which
// |exclude_predicate_| returns false).
bool IsSupportedFeature(const std::string& name, int platform_mask) const;
const FeatureEntry* feature_entries_; const FeatureEntry* feature_entries_;
size_t num_feature_entries_; size_t num_feature_entries_;
...@@ -220,6 +233,11 @@ class FlagsState { ...@@ -220,6 +233,11 @@ class FlagsState {
// were appended to existing (list value) switches. // were appended to existing (list value) switches.
std::map<std::string, std::set<std::string>> appended_switches_; std::map<std::string, std::set<std::string>> appended_switches_;
// Used as a predicate to exclude FeatureEntries from applying to
// switches or base::Features; those for which this predicate returns true
// will not have any effect.
base::RepeatingCallback<bool(const FeatureEntry&)> exclude_predicate_;
DISALLOW_COPY_AND_ASSIGN(FlagsState); DISALLOW_COPY_AND_ASSIGN(FlagsState);
}; };
......
...@@ -164,17 +164,24 @@ class FlagsStateTest : public ::testing::Test { ...@@ -164,17 +164,24 @@ class FlagsStateTest : public ::testing::Test {
while (os_other_than_current == FlagsState::GetCurrentPlatform()) while (os_other_than_current == FlagsState::GetCurrentPlatform())
os_other_than_current <<= 1; os_other_than_current <<= 1;
kEntries[2].supported_platforms = os_other_than_current; kEntries[2].supported_platforms = os_other_than_current;
flags_state_.reset(new FlagsState(kEntries, base::size(kEntries))); flags_state_.reset(new FlagsState(
kEntries, base::size(kEntries),
base::Bind(&FlagsStateTest::IsFlagExcluded, base::Unretained(this))));
} }
~FlagsStateTest() override { ~FlagsStateTest() override {
variations::testing::ClearAllVariationParams(); variations::testing::ClearAllVariationParams();
} }
bool IsFlagExcluded(const FeatureEntry& entry) {
return exclude_flags_.count(entry.internal_name) != 0;
}
TestingPrefServiceSimple prefs_; TestingPrefServiceSimple prefs_;
PrefServiceFlagsStorage flags_storage_; PrefServiceFlagsStorage flags_storage_;
std::unique_ptr<FlagsState> flags_state_; std::unique_ptr<FlagsState> flags_state_;
base::FieldTrialList trial_list_; base::FieldTrialList trial_list_;
std::set<std::string> exclude_flags_;
}; };
TEST_F(FlagsStateTest, NoChangeNoRestart) { TEST_F(FlagsStateTest, NoChangeNoRestart) {
...@@ -826,6 +833,20 @@ TEST_F(FlagsStateTest, EnableDisableValues) { ...@@ -826,6 +833,20 @@ TEST_F(FlagsStateTest, EnableDisableValues) {
EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1)); EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch1));
EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2)); EXPECT_FALSE(command_line.HasSwitch(kMultiSwitch2));
} }
// "Disable" option selected, but flag filtered out by exclude predicate.
flags_state_->SetFeatureEntryEnabled(&flags_storage_, entry.NameForOption(2),
true);
exclude_flags_.insert(entry.internal_name);
{
base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
flags_state_->ConvertFlagsToSwitches(&flags_storage_, &command_line,
kAddSentinels, kEnableFeatures,
kDisableFeatures);
EXPECT_FALSE(command_line.HasSwitch(kSwitch1));
EXPECT_FALSE(command_line.HasSwitch(kSwitch2));
}
exclude_flags_.clear();
} }
TEST_F(FlagsStateTest, FeatureValues) { TEST_F(FlagsStateTest, FeatureValues) {
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/base_switches.h" #include "base/base_switches.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/no_destructor.h" #include "base/no_destructor.h"
...@@ -636,7 +637,7 @@ bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) { ...@@ -636,7 +637,7 @@ bool SkipConditionalFeatureEntry(const flags_ui::FeatureEntry& entry) {
flags_ui::FlagsState& GetGlobalFlagsState() { flags_ui::FlagsState& GetGlobalFlagsState() {
static base::NoDestructor<flags_ui::FlagsState> flags_state( static base::NoDestructor<flags_ui::FlagsState> flags_state(
kFeatureEntries, base::size(kFeatureEntries)); kFeatureEntries, base::size(kFeatureEntries), base::NullCallback());
return *flags_state; return *flags_state;
} }
} // namespace } // namespace
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/base_switches.h" #include "base/base_switches.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -87,7 +88,7 @@ const flags_ui::FeatureEntry kFeatureEntries[] = { ...@@ -87,7 +88,7 @@ const flags_ui::FeatureEntry kFeatureEntries[] = {
std::make_unique<flags_ui::PrefServiceFlagsStorage>(_prefService); std::make_unique<flags_ui::PrefServiceFlagsStorage>(_prefService);
_flagsState = std::make_unique<flags_ui::FlagsState>( _flagsState = std::make_unique<flags_ui::FlagsState>(
ios_web_view::kFeatureEntries, ios_web_view::kFeatureEntries,
base::size(ios_web_view::kFeatureEntries)); base::size(ios_web_view::kFeatureEntries), base::NullCallback());
} }
return self; return self;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment