Commit 23f765cc authored by rdevlin.cronin's avatar rdevlin.cronin Committed by Commit bot

[Extensions] Add a SimpleFeature::Validate function

Currently, most feature validation happens in Parse(). Add a Validate()
function that can be called after Parse() so that if a feature is
generated, validation can still be performed (independent of whether or
not the feature is created from a JSON value).  In the future, these
validations should be performed as part of the compile process, but this
is a starting point.

Also update tests that parsed features and used JSONFeatureProvider only
to test this validation. This way, once we move away from
JSON-based features and JSONFeatureProvider, these tests are still
active.

BUG=280286

Review-Url: https://codereview.chromium.org/2150193002
Cr-Commit-Position: refs/heads/master@{#406076}
parent ee492c43
...@@ -676,36 +676,16 @@ TEST(ExtensionAPITest, DefaultConfigurationFeatures) { ...@@ -676,36 +676,16 @@ TEST(ExtensionAPITest, DefaultConfigurationFeatures) {
} }
} }
TEST(ExtensionAPITest, FeaturesRequireContexts) { TEST(ExtensionAPITest, JSONFeatureProviderDoesNotStoreInvalidFeatures) {
// TODO(cduvall): Make this check API featues. base::DictionaryValue features;
std::unique_ptr<base::DictionaryValue> api_features1( std::unique_ptr<base::DictionaryValue> feature_value(
new base::DictionaryValue()); new base::DictionaryValue());
std::unique_ptr<base::DictionaryValue> api_features2( // This feature is invalid (it needs an extension context), so the
new base::DictionaryValue()); // JSONFeatureProvider should not store it.
base::DictionaryValue* test1 = new base::DictionaryValue(); feature_value->SetString("channel", "stable");
base::DictionaryValue* test2 = new base::DictionaryValue(); features.Set("test", std::move(feature_value));
base::ListValue* contexts = new base::ListValue(); JSONFeatureProvider api_feature_provider(features, CreateAPIFeature);
contexts->AppendString("content_script"); EXPECT_FALSE(api_feature_provider.GetFeature("test"));
test1->Set("contexts", contexts);
test1->SetString("channel", "stable");
test2->SetString("channel", "stable");
api_features1->Set("test", test1);
api_features2->Set("test", test2);
struct {
base::DictionaryValue* api_features;
bool expect_success;
} test_data[] = {
{ api_features1.get(), true },
{ api_features2.get(), false }
};
for (size_t i = 0; i < arraysize(test_data); ++i) {
JSONFeatureProvider api_feature_provider(*test_data[i].api_features,
CreateAPIFeature);
Feature* feature = api_feature_provider.GetFeature("test");
EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i;
}
} }
static void GetDictionaryFromList(const base::DictionaryValue* schema, static void GetDictionaryFromList(const base::DictionaryValue* schema,
......
...@@ -6,27 +6,21 @@ ...@@ -6,27 +6,21 @@
namespace extensions { namespace extensions {
APIFeature::APIFeature() APIFeature::APIFeature() {}
: internal_(false) {}
APIFeature::~APIFeature() { APIFeature::~APIFeature() {
} }
bool APIFeature::IsInternal() const { bool APIFeature::Validate(std::string* error) {
return internal_; if (!SimpleFeature::Validate(error))
} return false;
std::string APIFeature::Parse(const base::DictionaryValue* value) {
std::string error = SimpleFeature::Parse(value);
if (!error.empty())
return error;
value->GetBoolean("internal", &internal_);
if (contexts()->empty()) if (contexts()->empty()) {
return name() + ": API features must specify at least one context."; *error = name() + ": API features must specify at least one context.";
return false;
}
return std::string(); return true;
} }
} // namespace extensions } // namespace extensions
...@@ -17,12 +17,7 @@ class APIFeature : public SimpleFeature { ...@@ -17,12 +17,7 @@ class APIFeature : public SimpleFeature {
~APIFeature() override; ~APIFeature() override;
// extensions::Feature: // extensions::Feature:
bool IsInternal() const override; bool Validate(std::string* error) override;
std::string Parse(const base::DictionaryValue* value) override;
private:
bool internal_;
}; };
} // namespace extensions } // namespace extensions
......
...@@ -35,10 +35,12 @@ bool ParseFeature(const base::DictionaryValue* value, ...@@ -35,10 +35,12 @@ bool ParseFeature(const base::DictionaryValue* value,
const std::string& name, const std::string& name,
SimpleFeature* feature) { SimpleFeature* feature) {
feature->set_name(name); feature->set_name(name);
std::string error = feature->Parse(value); feature->Parse(value);
if (!error.empty()) std::string error;
bool valid = feature->Validate(&error);
if (!valid)
LOG(ERROR) << error; LOG(ERROR) << error;
return error.empty(); return valid;
} }
} // namespace } // namespace
......
...@@ -34,20 +34,22 @@ Feature::Availability ManifestFeature::IsAvailableToContext( ...@@ -34,20 +34,22 @@ Feature::Availability ManifestFeature::IsAvailableToContext(
return CreateAvailability(IS_AVAILABLE); return CreateAvailability(IS_AVAILABLE);
} }
std::string ManifestFeature::Parse(const base::DictionaryValue* value) { bool ManifestFeature::Validate(std::string* error) {
std::string error = SimpleFeature::Parse(value); if (!SimpleFeature::Validate(error))
if (!error.empty()) return false;
return error;
if (extension_types()->empty()) { if (extension_types()->empty()) {
return name() + ": Manifest features must specify at least one " + *error = name() + ": Manifest features must specify at least one " +
"value for extension_types."; "value for extension_types.";
return false;
} }
if (value->HasKey("contexts")) if (!contexts()->empty()) {
return name() + ": Manifest features do not support contexts."; *error = name() + ": Manifest features do not support contexts.";
return false;
}
return std::string(); return true;
} }
} // namespace extensions } // namespace extensions
...@@ -22,7 +22,7 @@ class ManifestFeature : public SimpleFeature { ...@@ -22,7 +22,7 @@ class ManifestFeature : public SimpleFeature {
const GURL& url, const GURL& url,
Feature::Platform platform) const override; Feature::Platform platform) const override;
std::string Parse(const base::DictionaryValue* value) override; bool Validate(std::string* error) override;
}; };
} // namespace extensions } // namespace extensions
......
...@@ -33,20 +33,22 @@ Feature::Availability PermissionFeature::IsAvailableToContext( ...@@ -33,20 +33,22 @@ Feature::Availability PermissionFeature::IsAvailableToContext(
return CreateAvailability(IS_AVAILABLE); return CreateAvailability(IS_AVAILABLE);
} }
std::string PermissionFeature::Parse(const base::DictionaryValue* value) { bool PermissionFeature::Validate(std::string* error) {
std::string error = SimpleFeature::Parse(value); if (!SimpleFeature::Validate(error))
if (!error.empty()) return false;
return error;
if (extension_types()->empty()) { if (extension_types()->empty()) {
return name() + ": Permission features must specify at least one " + *error = name() + ": Permission features must specify at least one " +
"value for extension_types."; "value for extension_types.";
return false;
} }
if (value->HasKey("contexts")) if (!contexts()->empty()) {
return name() + ": Permission features do not support contexts."; *error = name() + ": Permission features do not support contexts.";
return false;
}
return std::string(); return true;
} }
} // namespace extensions } // namespace extensions
...@@ -21,8 +21,7 @@ class PermissionFeature : public SimpleFeature { ...@@ -21,8 +21,7 @@ class PermissionFeature : public SimpleFeature {
Feature::Context context, Feature::Context context,
const GURL& url, const GURL& url,
Feature::Platform platform) const override; Feature::Platform platform) const override;
bool Validate(std::string* error) override;
std::string Parse(const base::DictionaryValue* value) override;
}; };
} // namespace extensions } // namespace extensions
......
...@@ -310,11 +310,12 @@ SimpleFeature::SimpleFeature() ...@@ -310,11 +310,12 @@ SimpleFeature::SimpleFeature()
: location_(UNSPECIFIED_LOCATION), : location_(UNSPECIFIED_LOCATION),
min_manifest_version_(0), min_manifest_version_(0),
max_manifest_version_(0), max_manifest_version_(0),
component_extensions_auto_granted_(true) {} component_extensions_auto_granted_(true),
internal_(false) {}
SimpleFeature::~SimpleFeature() {} SimpleFeature::~SimpleFeature() {}
std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) { void SimpleFeature::Parse(const base::DictionaryValue* dictionary) {
static base::LazyInstance<SimpleFeature::Mappings> mappings = static base::LazyInstance<SimpleFeature::Mappings> mappings =
LAZY_INSTANCE_INITIALIZER; LAZY_INSTANCE_INITIALIZER;
...@@ -358,6 +359,8 @@ std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) { ...@@ -358,6 +359,8 @@ std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) {
channel_.reset(new version_info::Channel(version_info::Channel::UNKNOWN)); channel_.reset(new version_info::Channel(version_info::Channel::UNKNOWN));
ParseEnum<version_info::Channel>(value, channel_.get(), ParseEnum<version_info::Channel>(value, channel_.get(),
mappings.Get().channels); mappings.Get().channels);
} else if (key == "internal") {
value->GetAsBoolean(&internal_);
} }
} }
...@@ -369,13 +372,18 @@ std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) { ...@@ -369,13 +372,18 @@ std::string SimpleFeature::Parse(const base::DictionaryValue* dictionary) {
// and "matches" google.com/*. Then a sub-feature "foo.bar" might override // and "matches" google.com/*. Then a sub-feature "foo.bar" might override
// "matches" to be chromium.org/*. That sub-feature doesn't need to specify // "matches" to be chromium.org/*. That sub-feature doesn't need to specify
// "web_page" context because it's inherited, but we don't know that here. // "web_page" context because it's inherited, but we don't know that here.
}
bool SimpleFeature::Validate(std::string* error) {
DCHECK(error);
// All features must be channel-restricted, either directly or through // All features must be channel-restricted, either directly or through
// dependents. // dependents.
if (!channel_ && dependencies_.empty()) if (!channel_ && dependencies_.empty()) {
return name() + ": Must supply a value for channel or dependencies."; *error = name() + ": Must supply a value for channel or dependencies.";
return false;
}
return std::string(); return true;
} }
Feature::Availability SimpleFeature::IsAvailableToManifest( Feature::Availability SimpleFeature::IsAvailableToManifest(
...@@ -583,7 +591,7 @@ Feature::Availability SimpleFeature::CreateAvailability( ...@@ -583,7 +591,7 @@ Feature::Availability SimpleFeature::CreateAvailability(
} }
bool SimpleFeature::IsInternal() const { bool SimpleFeature::IsInternal() const {
return false; return internal_;
} }
bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const { bool SimpleFeature::IsIdInBlacklist(const std::string& extension_id) const {
......
...@@ -47,10 +47,13 @@ class SimpleFeature : public Feature { ...@@ -47,10 +47,13 @@ class SimpleFeature : public Feature {
~SimpleFeature() override; ~SimpleFeature() override;
// Parses the JSON representation of a feature into the fields of this object. // Parses the JSON representation of a feature into the fields of this object.
// Unspecified values in the JSON are not modified in the object. This allows // Note: Validate() should be called after this.
// us to implement inheritance by parsing one value after another. Returns void Parse(const base::DictionaryValue* dictionary);
// the error found, or an empty string on success.
virtual std::string Parse(const base::DictionaryValue* dictionary); // Checks whether the feature is valid. Invalid features should not be used.
// Subclasses can override to implement specific checking, but should always
// call this method as well.
virtual bool Validate(std::string* error);
Availability IsAvailableToContext(const Extension* extension, Availability IsAvailableToContext(const Extension* extension,
Context context) const { Context context) const {
...@@ -153,7 +156,9 @@ class SimpleFeature : public Feature { ...@@ -153,7 +156,9 @@ class SimpleFeature : public Feature {
FRIEND_TEST_ALL_PREFIXES(ManifestUnitTest, Extension); FRIEND_TEST_ALL_PREFIXES(ManifestUnitTest, Extension);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Blacklist); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Blacklist);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, CommandLineSwitch); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, CommandLineSwitch);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ComplexFeatureAvailability);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Context); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Context);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, FeatureValidation);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, HashedIdBlacklist); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, HashedIdBlacklist);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, HashedIdWhitelist); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, HashedIdWhitelist);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Inheritance); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Inheritance);
...@@ -168,6 +173,7 @@ class SimpleFeature : public Feature { ...@@ -168,6 +173,7 @@ class SimpleFeature : public Feature {
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParsePlatforms); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParsePlatforms);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseWhitelist); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, ParseWhitelist);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Platform); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Platform);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, SimpleFeatureAvailability);
FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Whitelist); FRIEND_TEST_ALL_PREFIXES(SimpleFeatureTest, Whitelist);
// Holds String to Enum value mappings. // Holds String to Enum value mappings.
...@@ -200,6 +206,7 @@ class SimpleFeature : public Feature { ...@@ -200,6 +206,7 @@ class SimpleFeature : public Feature {
bool component_extensions_auto_granted_; bool component_extensions_auto_granted_;
std::string command_line_switch_; std::string command_line_switch_;
std::unique_ptr<version_info::Channel> channel_; std::unique_ptr<version_info::Channel> channel_;
bool internal_;
DISALLOW_COPY_AND_ASSIGN(SimpleFeature); DISALLOW_COPY_AND_ASSIGN(SimpleFeature);
}; };
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/test/scoped_command_line.h" #include "base/test/scoped_command_line.h"
#include "base/values.h" #include "base/values.h"
#include "extensions/common/features/api_feature.h"
#include "extensions/common/features/complex_feature.h" #include "extensions/common/features/complex_feature.h"
#include "extensions/common/features/feature_channel.h" #include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/json_feature_provider.h" #include "extensions/common/features/json_feature_provider.h"
...@@ -841,77 +842,62 @@ TEST_F(SimpleFeatureTest, SupportedChannel) { ...@@ -841,77 +842,62 @@ TEST_F(SimpleFeatureTest, SupportedChannel) {
// Tests the validation of features with channel entries. // Tests the validation of features with channel entries.
TEST_F(SimpleFeatureTest, FeatureValidation) { TEST_F(SimpleFeatureTest, FeatureValidation) {
std::unique_ptr<base::DictionaryValue> value(new base::DictionaryValue()); std::string error;
{
base::DictionaryValue* feature1 = new base::DictionaryValue(); PermissionFeature feature;
feature1->SetString("channel", "trunk"); feature.channel_.reset(
value->Set("feature1", feature1); new version_info::Channel(version_info::Channel::UNKNOWN));
// The feature won't validate because it lacks an extension type.
base::DictionaryValue* feature2 = new base::DictionaryValue(); EXPECT_FALSE(feature.Validate(&error));
feature2->SetString("channel", "trunk"); // If we add one, it works.
base::ListValue* extension_types = new base::ListValue(); feature.extension_types_.push_back(Manifest::TYPE_EXTENSION);
extension_types->AppendString("extension"); EXPECT_TRUE(feature.Validate(&error));
feature2->Set("extension_types", extension_types); // Remove the channel, and feature1 won't validate.
base::ListValue* contexts = new base::ListValue(); feature.channel_.reset();
contexts->AppendString("blessed_extension"); EXPECT_FALSE(feature.Validate(&error));
feature2->Set("contexts", contexts); }
value->Set("feature2", feature2); {
PermissionFeature feature;
std::unique_ptr<JSONFeatureProvider> provider( feature.channel_.reset(
new JSONFeatureProvider(*value, CreateFeature<PermissionFeature>)); new version_info::Channel(version_info::Channel::UNKNOWN));
feature.extension_types_.push_back(Manifest::TYPE_EXTENSION);
// feature1 won't validate because it lacks an extension type. feature.contexts_.push_back(Feature::BLESSED_EXTENSION_CONTEXT);
EXPECT_FALSE(provider->GetFeature("feature1")); // The feature won't validate because of the presence of "contexts".
EXPECT_FALSE(feature.Validate(&error));
// If we add one, it works. feature.contexts_.clear();
feature1->Set("extension_types", extension_types->DeepCopy()); EXPECT_TRUE(feature.Validate(&error));
provider.reset( }
new JSONFeatureProvider(*value, CreateFeature<PermissionFeature>)); {
EXPECT_TRUE(provider->GetFeature("feature1")); APIFeature feature;
feature.channel_.reset(
// Remove the channel, and feature1 won't validate. new version_info::Channel(version_info::Channel::STABLE));
feature1->Remove("channel", NULL); // API features require contexts.
provider.reset( EXPECT_FALSE(feature.Validate(&error));
new JSONFeatureProvider(*value, CreateFeature<PermissionFeature>)); feature.contexts_.push_back(Feature::CONTENT_SCRIPT_CONTEXT);
EXPECT_FALSE(provider->GetFeature("feature1")); EXPECT_TRUE(feature.Validate(&error));
}
// feature2 won't validate because of the presence of "contexts".
EXPECT_FALSE(provider->GetFeature("feature2"));
// If we remove it, it works.
feature2->Remove("contexts", NULL);
provider.reset(
new JSONFeatureProvider(*value, CreateFeature<PermissionFeature>));
EXPECT_TRUE(provider->GetFeature("feature2"));
} }
// Tests simple feature availability across channels. // Tests simple feature availability across channels.
TEST_F(SimpleFeatureTest, SimpleFeatureAvailability) { TEST_F(SimpleFeatureTest, SimpleFeatureAvailability) {
std::unique_ptr<base::DictionaryValue> rule( std::unique_ptr<ComplexFeature> complex_feature;
DictionaryBuilder() {
.Set("feature1", std::unique_ptr<SimpleFeature> feature1(CreateFeature<SimpleFeature>());
ListBuilder() feature1->channel_.reset(
.Append(DictionaryBuilder() new version_info::Channel(version_info::Channel::BETA));
.Set("channel", "beta") feature1->extension_types_.push_back(Manifest::TYPE_EXTENSION);
.Set("extension_types", std::unique_ptr<SimpleFeature> feature2(CreateFeature<SimpleFeature>());
ListBuilder().Append("extension").Build()) feature2->channel_.reset(
.Build()) new version_info::Channel(version_info::Channel::BETA));
.Append(DictionaryBuilder() feature2->extension_types_.push_back(Manifest::TYPE_LEGACY_PACKAGED_APP);
.Set("channel", "beta") std::unique_ptr<ComplexFeature::FeatureList> list(
.Set("extension_types", new ComplexFeature::FeatureList());
ListBuilder() list->push_back(std::move(feature1));
.Append("legacy_packaged_app") list->push_back(std::move(feature2));
.Build()) complex_feature.reset(new ComplexFeature(std::move(list)));
.Build()) }
.Build())
.Build());
std::unique_ptr<JSONFeatureProvider> provider(
new JSONFeatureProvider(*rule, CreateFeature<SimpleFeature>));
Feature* feature = provider->GetFeature("feature1");
EXPECT_TRUE(feature);
Feature* feature = static_cast<Feature*>(complex_feature.get());
// Make sure both rules are applied correctly. // Make sure both rules are applied correctly.
{ {
ScopedCurrentChannel current_channel(version_info::Channel::BETA); ScopedCurrentChannel current_channel(version_info::Channel::BETA);
...@@ -947,66 +933,52 @@ TEST_F(SimpleFeatureTest, SimpleFeatureAvailability) { ...@@ -947,66 +933,52 @@ TEST_F(SimpleFeatureTest, SimpleFeatureAvailability) {
// Tests complex feature availability across channels. // Tests complex feature availability across channels.
TEST_F(SimpleFeatureTest, ComplexFeatureAvailability) { TEST_F(SimpleFeatureTest, ComplexFeatureAvailability) {
std::unique_ptr<ComplexFeature::FeatureList> features( std::unique_ptr<ComplexFeature> complex_feature;
new ComplexFeature::FeatureList());
// Rule: "extension", channel trunk.
std::unique_ptr<SimpleFeature> simple_feature(CreateFeature<SimpleFeature>());
std::unique_ptr<base::DictionaryValue> rule(
DictionaryBuilder()
.Set("channel", "trunk")
.Set("extension_types", ListBuilder().Append("extension").Build())
.Build());
simple_feature->Parse(rule.get());
features->push_back(std::move(simple_feature));
// Rule: "legacy_packaged_app", channel stable.
simple_feature.reset(CreateFeature<SimpleFeature>());
rule = DictionaryBuilder()
.Set("channel", "stable")
.Set("extension_types",
ListBuilder().Append("legacy_packaged_app").Build())
.Build();
simple_feature->Parse(rule.get());
features->push_back(std::move(simple_feature));
std::unique_ptr<ComplexFeature> feature(
new ComplexFeature(std::move(features)));
// Test match 1st rule.
{ {
ScopedCurrentChannel current_channel(version_info::Channel::UNKNOWN); // Rule: "extension", channel trunk.
EXPECT_EQ( std::unique_ptr<SimpleFeature> feature1(CreateFeature<SimpleFeature>());
Feature::IS_AVAILABLE, feature1->channel_.reset(
feature->IsAvailableToManifest("1", new version_info::Channel(version_info::Channel::UNKNOWN));
Manifest::TYPE_EXTENSION, feature1->extension_types_.push_back(Manifest::TYPE_EXTENSION);
Manifest::INVALID_LOCATION, std::unique_ptr<SimpleFeature> feature2(CreateFeature<SimpleFeature>());
Feature::UNSPECIFIED_PLATFORM, // Rule: "legacy_packaged_app", channel stable.
Feature::GetCurrentPlatform()).result()); feature2->channel_.reset(
new version_info::Channel(version_info::Channel::STABLE));
feature2->extension_types_.push_back(Manifest::TYPE_LEGACY_PACKAGED_APP);
std::unique_ptr<ComplexFeature::FeatureList> list(
new ComplexFeature::FeatureList());
list->push_back(std::move(feature1));
list->push_back(std::move(feature2));
complex_feature.reset(new ComplexFeature(std::move(list)));
} }
// Test match 2nd rule. Feature* feature = static_cast<Feature*>(complex_feature.get());
{
ScopedCurrentChannel current_channel(version_info::Channel::UNKNOWN);
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest("1", Manifest::TYPE_EXTENSION,
Manifest::INVALID_LOCATION,
Feature::UNSPECIFIED_PLATFORM)
.result());
}
{ {
ScopedCurrentChannel current_channel(version_info::Channel::BETA); ScopedCurrentChannel current_channel(version_info::Channel::BETA);
EXPECT_EQ( EXPECT_EQ(Feature::IS_AVAILABLE,
Feature::IS_AVAILABLE, feature
feature->IsAvailableToManifest("2", ->IsAvailableToManifest(
Manifest::TYPE_LEGACY_PACKAGED_APP, "2", Manifest::TYPE_LEGACY_PACKAGED_APP,
Manifest::INVALID_LOCATION, Manifest::INVALID_LOCATION, Feature::UNSPECIFIED_PLATFORM)
Feature::UNSPECIFIED_PLATFORM, .result());
Feature::GetCurrentPlatform()).result());
} }
// Test feature not available to extensions above channel unknown.
{ {
ScopedCurrentChannel current_channel(version_info::Channel::BETA); ScopedCurrentChannel current_channel(version_info::Channel::BETA);
EXPECT_NE( EXPECT_NE(Feature::IS_AVAILABLE,
Feature::IS_AVAILABLE, feature
feature->IsAvailableToManifest("1", ->IsAvailableToManifest("1", Manifest::TYPE_EXTENSION,
Manifest::TYPE_EXTENSION, Manifest::INVALID_LOCATION,
Manifest::INVALID_LOCATION, Feature::UNSPECIFIED_PLATFORM)
Feature::UNSPECIFIED_PLATFORM, .result());
Feature::GetCurrentPlatform()).result());
} }
} }
......
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