blob: 293b14602bf5fa2ba4671135155e1ae4676371e2 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "extensions/common/features/simple_feature.h"
#include <stddef.h>
#include <array>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "content/public/common/content_features.h"
#include "content/public/test/test_utils.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/features/complex_feature.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_channel.h"
#include "extensions/common/features/feature_developer_mode_only.h"
#include "extensions/common/features/feature_flags.h"
#include "extensions/common/features/feature_session_type.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_handlers/background_info.h"
#include "extensions/common/mojom/context_type.mojom.h"
#include "extensions/common/switches.h"
#include "extensions/test/test_context_data.h"
#include "testing/gtest/include/gtest/gtest.h"
using extensions::mojom::ManifestLocation;
using version_info::Channel;
namespace extensions {
namespace {
struct IsAvailableTestData {
ExtensionId extension_id;
Manifest::Type extension_type;
ManifestLocation location;
Feature::Platform platform;
int manifest_version;
int context_id;
Feature::AvailabilityResult expected_result;
};
struct FeatureSessionTypeTestData {
std::string desc;
Feature::AvailabilityResult expected_availability;
mojom::FeatureSessionType current_session_type;
std::initializer_list<mojom::FeatureSessionType> feature_session_types;
};
Feature::AvailabilityResult IsAvailableInChannel(
std::optional<Channel> channel_for_feature,
Channel channel_for_testing) {
ScopedCurrentChannel current_channel(channel_for_testing);
SimpleFeature feature;
if (channel_for_feature.has_value()) {
feature.set_channel(channel_for_feature.value());
}
return feature
.IsAvailableToManifest(
HashedExtensionId(std::string(32, 'a')), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1, Feature::GetCurrentPlatform(),
kUnspecifiedContextId)
.result();
}
} // namespace
class SimpleFeatureTest : public testing::Test {
public:
SimpleFeatureTest(const SimpleFeatureTest&) = delete;
SimpleFeatureTest& operator=(const SimpleFeatureTest&) = delete;
protected:
SimpleFeatureTest() : current_channel_(Channel::UNKNOWN) {}
bool LocationIsAvailable(SimpleFeature::Location feature_location,
ManifestLocation manifest_location) {
SimpleFeature feature;
feature.set_location(feature_location);
Feature::AvailabilityResult availability_result =
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN, manifest_location,
-1, Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result();
return availability_result == Feature::IS_AVAILABLE;
}
private:
ScopedCurrentChannel current_channel_;
};
TEST_F(SimpleFeatureTest, IsAvailableNullCase) {
const auto tests = std::to_array<IsAvailableTestData>({
{"", Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM, -1, kUnspecifiedContextId,
Feature::IS_AVAILABLE},
{"random-extension", Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, Feature::UNSPECIFIED_PLATFORM, -1,
kUnspecifiedContextId, Feature::IS_AVAILABLE},
{"", Manifest::TYPE_LEGACY_PACKAGED_APP,
ManifestLocation::kInvalidLocation, Feature::UNSPECIFIED_PLATFORM, -1,
kUnspecifiedContextId, Feature::IS_AVAILABLE},
{"", Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM, -1, kUnspecifiedContextId,
Feature::IS_AVAILABLE},
{"", Manifest::TYPE_UNKNOWN, ManifestLocation::kComponent,
Feature::UNSPECIFIED_PLATFORM, -1, kUnspecifiedContextId,
Feature::IS_AVAILABLE},
{"", Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
Feature::CHROMEOS_PLATFORM, -1, kUnspecifiedContextId,
Feature::IS_AVAILABLE},
{"", Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM, 25, kUnspecifiedContextId,
Feature::IS_AVAILABLE},
});
SimpleFeature feature;
for (const auto& test : tests) {
EXPECT_EQ(test.expected_result,
feature
.IsAvailableToManifest(HashedExtensionId(test.extension_id),
test.extension_type, test.location,
test.manifest_version, test.platform,
test.context_id)
.result());
}
}
TEST_F(SimpleFeatureTest, Allowlist) {
const HashedExtensionId kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
const HashedExtensionId kIdBar("barabbbbccccddddeeeeffffgggghhhh");
const HashedExtensionId kIdBaz("bazabbbbccccddddeeeeffffgggghhhh");
SimpleFeature feature;
feature.set_allowlist({kIdFoo.value().c_str(), kIdBar.value().c_str()});
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(kIdFoo, Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(kIdBar, Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::NOT_FOUND_IN_ALLOWLIST,
feature
.IsAvailableToManifest(kIdBaz, Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::NOT_FOUND_IN_ALLOWLIST,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
feature.set_extension_types({Manifest::TYPE_LEGACY_PACKAGED_APP});
EXPECT_EQ(Feature::NOT_FOUND_IN_ALLOWLIST,
feature
.IsAvailableToManifest(
kIdBaz, Manifest::TYPE_LEGACY_PACKAGED_APP,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, HashedIdAllowlist) {
// echo -n "fooabbbbccccddddeeeeffffgggghhhh" |
// sha1sum | tr '[:lower:]' '[:upper:]'
const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
const std::string kIdFooHashed("55BC7228A0D502A2A48C9BB16B07062A01E62897");
SimpleFeature feature;
feature.set_allowlist({kIdFooHashed.c_str()});
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(kIdFoo), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_NE(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(kIdFooHashed), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::NOT_FOUND_IN_ALLOWLIST,
feature
.IsAvailableToManifest(
HashedExtensionId("slightlytoooolongforanextensionid"),
Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
-1, Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::NOT_FOUND_IN_ALLOWLIST,
feature
.IsAvailableToManifest(
HashedExtensionId("tooshortforanextensionid"),
Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
-1, Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, Blocklist) {
const HashedExtensionId kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
const HashedExtensionId kIdBar("barabbbbccccddddeeeeffffgggghhhh");
const HashedExtensionId kIdBaz("bazabbbbccccddddeeeeffffgggghhhh");
SimpleFeature feature;
feature.set_blocklist({kIdFoo.value().c_str(), kIdBar.value().c_str()});
EXPECT_EQ(Feature::FOUND_IN_BLOCKLIST,
feature
.IsAvailableToManifest(kIdFoo, Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::FOUND_IN_BLOCKLIST,
feature
.IsAvailableToManifest(kIdBar, Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(kIdBaz, Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, HashedIdBlocklist) {
// echo -n "fooabbbbccccddddeeeeffffgggghhhh" |
// sha1sum | tr '[:lower:]' '[:upper:]'
const std::string kIdFoo("fooabbbbccccddddeeeeffffgggghhhh");
const std::string kIdFooHashed("55BC7228A0D502A2A48C9BB16B07062A01E62897");
SimpleFeature feature;
feature.set_blocklist({kIdFooHashed.c_str()});
EXPECT_EQ(Feature::FOUND_IN_BLOCKLIST,
feature
.IsAvailableToManifest(
HashedExtensionId(kIdFoo), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_NE(Feature::FOUND_IN_BLOCKLIST,
feature
.IsAvailableToManifest(
HashedExtensionId(kIdFooHashed), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId("slightlytoooolongforanextensionid"),
Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
-1, Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId("tooshortforanextensionid"),
Manifest::TYPE_UNKNOWN, ManifestLocation::kInvalidLocation,
-1, Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, PackageType) {
SimpleFeature feature;
feature.set_extension_types(
{Manifest::TYPE_EXTENSION, Manifest::TYPE_LEGACY_PACKAGED_APP});
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_EXTENSION,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_LEGACY_PACKAGED_APP,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::INVALID_TYPE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::INVALID_TYPE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_THEME,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, Context) {
SimpleFeature feature;
feature.set_name("somefeature");
feature.set_contexts({mojom::ContextType::kPrivilegedExtension});
feature.set_extension_types({Manifest::TYPE_LEGACY_PACKAGED_APP});
feature.set_platforms({Feature::CHROMEOS_PLATFORM});
feature.set_min_manifest_version(21);
feature.set_max_manifest_version(25);
auto manifest = base::Value::Dict()
.Set("name", "test")
.Set("version", "1")
.Set("manifest_version", 21);
manifest.SetByDottedPath("app.launch.local_path", "foo.html");
std::u16string error;
scoped_refptr<const Extension> extension(
Extension::Create(base::FilePath(), ManifestLocation::kInternal, manifest,
Extension::NO_FLAGS, &error));
EXPECT_EQ(u"", error);
ASSERT_TRUE(extension.get());
feature.set_allowlist({"monkey"});
EXPECT_EQ(Feature::NOT_FOUND_IN_ALLOWLIST,
feature
.IsAvailableToContext(extension.get(),
mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM,
kUnspecifiedContextId, TestContextData())
.result());
feature.set_allowlist({});
feature.set_extension_types({Manifest::TYPE_THEME});
{
Feature::Availability availability = feature.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId, TestContextData());
EXPECT_EQ(Feature::INVALID_TYPE, availability.result());
EXPECT_EQ("'somefeature' is only allowed for themes, "
"but this is a legacy packaged app.",
availability.message());
}
feature.set_extension_types({Manifest::TYPE_LEGACY_PACKAGED_APP});
feature.set_contexts({mojom::ContextType::kUnprivilegedExtension,
mojom::ContextType::kContentScript});
{
Feature::Availability availability = feature.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId, TestContextData());
EXPECT_EQ(Feature::INVALID_CONTEXT, availability.result());
EXPECT_EQ("'somefeature' is only allowed to run in extension iframes and "
"content scripts, but this is a privileged page",
availability.message());
}
feature.set_contexts({mojom::ContextType::kUnprivilegedExtension,
mojom::ContextType::kContentScript,
mojom::ContextType::kWebPage});
{
Feature::Availability availability = feature.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId, TestContextData());
EXPECT_EQ(Feature::INVALID_CONTEXT, availability.result());
EXPECT_EQ("'somefeature' is only allowed to run in extension iframes, "
"content scripts, and web pages, but this is a privileged page",
availability.message());
}
{
SimpleFeature other_feature;
other_feature.set_location(SimpleFeature::COMPONENT_LOCATION);
EXPECT_EQ(Feature::INVALID_LOCATION,
other_feature
.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId,
TestContextData())
.result());
}
feature.set_contexts({mojom::ContextType::kPrivilegedExtension});
EXPECT_EQ(Feature::INVALID_PLATFORM,
feature
.IsAvailableToContext(extension.get(),
mojom::ContextType::kPrivilegedExtension,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId, TestContextData())
.result());
feature.set_min_manifest_version(22);
EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
feature
.IsAvailableToContext(extension.get(),
mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM,
kUnspecifiedContextId, TestContextData())
.result());
feature.set_min_manifest_version(21);
feature.set_max_manifest_version(18);
EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION,
feature
.IsAvailableToContext(extension.get(),
mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM,
kUnspecifiedContextId, TestContextData())
.result());
feature.set_max_manifest_version(25);
}
TEST_F(SimpleFeatureTest, SessionType) {
auto manifest = base::Value::Dict()
.Set("name", "test")
.Set("version", "1")
.Set("manifest_version", 2);
manifest.SetByDottedPath("app.launch.local_path", "foo.html");
std::u16string error;
scoped_refptr<const Extension> extension(
Extension::Create(base::FilePath(), ManifestLocation::kInternal, manifest,
Extension::NO_FLAGS, &error));
EXPECT_EQ(u"", error);
ASSERT_TRUE(extension.get());
const auto kTestData = std::to_array<FeatureSessionTypeTestData>({
{"kiosk_feature in kiosk session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kKiosk,
{mojom::FeatureSessionType::kKiosk}},
{"kiosk feature in regular session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kRegular,
{mojom::FeatureSessionType::kKiosk}},
{"kiosk feature in unknown session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kUnknown,
{mojom::FeatureSessionType::kKiosk}},
{"kiosk feature in initial session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kInitial,
{mojom::FeatureSessionType::kKiosk}},
{"non kiosk feature in kiosk session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kKiosk,
{mojom::FeatureSessionType::kRegular}},
{"non kiosk feature in regular session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kRegular,
{mojom::FeatureSessionType::kRegular}},
{"non kiosk feature in unknown session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kUnknown,
{mojom::FeatureSessionType::kRegular}},
{"non kiosk feature in initial session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kInitial,
{mojom::FeatureSessionType::kRegular}},
{"session agnostic feature in kiosk session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kKiosk,
{}},
{"session agnostic feature in auto-launched kiosk session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kAutolaunchedKiosk,
{}},
{"session agnostic feature in regular session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kRegular,
{}},
{"session agnostic feature in unknown session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kUnknown,
{}},
{"feature with multiple session types",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kRegular,
{mojom::FeatureSessionType::kRegular,
mojom::FeatureSessionType::kKiosk}},
{"feature with multiple session types in unknown session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kUnknown,
{mojom::FeatureSessionType::kRegular,
mojom::FeatureSessionType::kKiosk}},
{"feature with multiple session types in initial session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kInitial,
{mojom::FeatureSessionType::kRegular,
mojom::FeatureSessionType::kKiosk}},
{"feature with auto-launched kiosk session type in regular session",
Feature::INVALID_SESSION_TYPE,
mojom::FeatureSessionType::kAutolaunchedKiosk,
{mojom::FeatureSessionType::kRegular}},
{"feature with auto-launched kiosk session type in auto-launched kiosk",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kAutolaunchedKiosk,
{mojom::FeatureSessionType::kAutolaunchedKiosk}},
{"feature with kiosk session type in auto-launched kiosk session",
Feature::IS_AVAILABLE,
mojom::FeatureSessionType::kAutolaunchedKiosk,
{mojom::FeatureSessionType::kKiosk}},
});
for (const auto& entry : kTestData) {
std::unique_ptr<base::AutoReset<mojom::FeatureSessionType>> current_session(
ScopedCurrentFeatureSessionType(entry.current_session_type));
SimpleFeature feature;
feature.set_session_types(entry.feature_session_types);
EXPECT_EQ(entry.expected_availability,
feature
.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId,
TestContextData())
.result())
<< "Failed test '" << entry.desc << "'.";
EXPECT_EQ(entry.expected_availability,
feature
.IsAvailableToManifest(
extension->hashed_id(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId)
.result())
<< "Failed test '" << entry.desc << "'.";
}
}
TEST_F(SimpleFeatureTest, Location) {
// Component extensions can access any location.
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kComponent));
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::EXTERNAL_COMPONENT_LOCATION,
ManifestLocation::kComponent));
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kComponent));
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::UNPACKED_LOCATION,
ManifestLocation::kComponent));
// Only component extensions can access the "component" location.
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kInvalidLocation));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kUnpacked));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kExternalComponent));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kExternalPrefDownload));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kExternalPolicy));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::COMPONENT_LOCATION,
ManifestLocation::kExternalPolicyDownload));
// Policy extensions can access the "policy" location.
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kExternalPolicy));
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kExternalPolicyDownload));
// Non-policy (except component) extensions cannot access policy.
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kExternalComponent));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kInvalidLocation));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kUnpacked));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::POLICY_LOCATION,
ManifestLocation::kExternalPrefDownload));
// External component extensions can access the "external_component"
// location.
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::EXTERNAL_COMPONENT_LOCATION,
ManifestLocation::kExternalComponent));
// Only unpacked and command line extensions can access the "unpacked"
// location.
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::UNPACKED_LOCATION,
ManifestLocation::kUnpacked));
EXPECT_TRUE(LocationIsAvailable(SimpleFeature::UNPACKED_LOCATION,
ManifestLocation::kCommandLine));
EXPECT_FALSE(LocationIsAvailable(SimpleFeature::UNPACKED_LOCATION,
ManifestLocation::kInternal));
}
TEST_F(SimpleFeatureTest, Platform) {
SimpleFeature feature;
feature.set_platforms({Feature::CHROMEOS_PLATFORM});
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::INVALID_PLATFORM,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, -1,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, ManifestVersion) {
SimpleFeature feature;
feature.set_min_manifest_version(5);
EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 0,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::INVALID_MIN_MANIFEST_VERSION,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 4,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 5,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 10,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
feature.set_max_manifest_version(8);
EXPECT_EQ(Feature::INVALID_MAX_MANIFEST_VERSION,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 10,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 8,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToManifest(
HashedExtensionId(), Manifest::TYPE_UNKNOWN,
ManifestLocation::kInvalidLocation, 7,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
TEST_F(SimpleFeatureTest, CommandLineSwitch) {
SimpleFeature feature;
feature.set_command_line_switch("laser-beams");
{
EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch("laser-beams");
EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
"enable-laser-beams");
EXPECT_EQ(Feature::IS_AVAILABLE,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
"disable-laser-beams");
EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch("laser-beams=1");
EXPECT_EQ(Feature::IS_AVAILABLE,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch("laser-beams=0");
EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
}
TEST_F(SimpleFeatureTest, FeatureFlags) {
static BASE_FEATURE(kStubFeature1, "StubFeature1",
base::FEATURE_ENABLED_BY_DEFAULT);
static BASE_FEATURE(kStubFeature2, "StubFeature2",
base::FEATURE_DISABLED_BY_DEFAULT);
const base::Feature* kOverriddenFeatures[] = {&kStubFeature1, &kStubFeature2};
auto scoped_feature_override =
CreateScopedFeatureFlagsOverrideForTesting(kOverriddenFeatures);
SimpleFeature simple_feature_1;
simple_feature_1.set_feature_flag(kStubFeature1.name);
EXPECT_EQ(Feature::IS_AVAILABLE,
simple_feature_1.IsAvailableToEnvironment(kUnspecifiedContextId)
.result());
SimpleFeature simple_feature_2;
simple_feature_2.set_feature_flag(kStubFeature2.name);
EXPECT_EQ(Feature::FEATURE_FLAG_DISABLED,
simple_feature_2.IsAvailableToEnvironment(kUnspecifiedContextId)
.result());
// Ensure we take any base::Feature overrides into account.
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures({kStubFeature2} /* enabled_features */,
{kStubFeature1} /* disabled_features */);
EXPECT_EQ(Feature::FEATURE_FLAG_DISABLED,
simple_feature_1.IsAvailableToEnvironment(kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
simple_feature_2.IsAvailableToEnvironment(kUnspecifiedContextId)
.result());
}
// Tests that all combinations of feature channel and Chrome channel correctly
// compute feature availability.
TEST_F(SimpleFeatureTest, SupportedChannel) {
// stable supported.
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::STABLE, Channel::UNKNOWN));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::STABLE, Channel::CANARY));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::STABLE, Channel::DEV));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::STABLE, Channel::BETA));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::STABLE, Channel::STABLE));
// beta supported.
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::BETA, Channel::UNKNOWN));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::BETA, Channel::CANARY));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::BETA, Channel::DEV));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::BETA, Channel::BETA));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::BETA, Channel::STABLE));
// dev supported.
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::DEV, Channel::UNKNOWN));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::DEV, Channel::CANARY));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::DEV, Channel::DEV));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::DEV, Channel::BETA));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::DEV, Channel::STABLE));
// canary supported.
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::CANARY, Channel::UNKNOWN));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::CANARY, Channel::CANARY));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::CANARY, Channel::DEV));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::CANARY, Channel::BETA));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::CANARY, Channel::STABLE));
// trunk supported.
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(Channel::UNKNOWN, Channel::UNKNOWN));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::UNKNOWN, Channel::CANARY));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::UNKNOWN, Channel::DEV));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::UNKNOWN, Channel::BETA));
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
IsAvailableInChannel(Channel::UNKNOWN, Channel::STABLE));
// Verify that a feature without a channel specified is available in all
// channels.
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(std::nullopt, Channel::UNKNOWN));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(std::nullopt, Channel::CANARY));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(std::nullopt, Channel::DEV));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(std::nullopt, Channel::BETA));
EXPECT_EQ(Feature::IS_AVAILABLE,
IsAvailableInChannel(std::nullopt, Channel::STABLE));
}
// Tests simple feature availability across channels.
TEST_F(SimpleFeatureTest, SimpleFeatureAvailability) {
std::unique_ptr<ComplexFeature> complex_feature;
{
std::unique_ptr<SimpleFeature> feature1(new SimpleFeature());
feature1->set_channel(Channel::BETA);
feature1->set_extension_types({Manifest::TYPE_EXTENSION});
std::unique_ptr<SimpleFeature> feature2(new SimpleFeature());
feature2->set_channel(Channel::BETA);
feature2->set_extension_types({Manifest::TYPE_LEGACY_PACKAGED_APP});
std::vector<Feature*> list;
list.push_back(feature1.release());
list.push_back(feature2.release());
complex_feature = std::make_unique<ComplexFeature>(&list);
}
Feature* feature = static_cast<Feature*>(complex_feature.get());
// Make sure both rules are applied correctly.
const HashedExtensionId kId1(std::string(32, 'a'));
const HashedExtensionId kId2(std::string(32, 'b'));
{
ScopedCurrentChannel current_channel(Channel::BETA);
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(kId1, Manifest::TYPE_EXTENSION,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(
kId2, Manifest::TYPE_LEGACY_PACKAGED_APP,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
{
ScopedCurrentChannel current_channel(Channel::STABLE);
EXPECT_NE(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(kId1, Manifest::TYPE_EXTENSION,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
EXPECT_NE(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(
kId2, Manifest::TYPE_LEGACY_PACKAGED_APP,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
}
// Tests complex feature availability across channels.
TEST_F(SimpleFeatureTest, ComplexFeatureAvailability) {
std::unique_ptr<ComplexFeature> complex_feature;
{
// Rule: "extension", channel trunk.
std::unique_ptr<SimpleFeature> feature1(new SimpleFeature());
feature1->set_channel(Channel::UNKNOWN);
feature1->set_extension_types({Manifest::TYPE_EXTENSION});
std::unique_ptr<SimpleFeature> feature2(new SimpleFeature());
// Rule: "legacy_packaged_app", channel stable.
feature2->set_channel(Channel::STABLE);
feature2->set_extension_types({Manifest::TYPE_LEGACY_PACKAGED_APP});
std::vector<Feature*> list;
list.push_back(feature1.release());
list.push_back(feature2.release());
complex_feature = std::make_unique<ComplexFeature>(&list);
}
const HashedExtensionId kId1(std::string(32, 'a'));
const HashedExtensionId kId2(std::string(32, 'b'));
Feature* feature = static_cast<Feature*>(complex_feature.get());
{
ScopedCurrentChannel current_channel(Channel::UNKNOWN);
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(kId1, Manifest::TYPE_EXTENSION,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
}
{
ScopedCurrentChannel current_channel(Channel::BETA);
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(
kId2, Manifest::TYPE_LEGACY_PACKAGED_APP,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM, kUnspecifiedContextId)
.result());
}
{
ScopedCurrentChannel current_channel(Channel::BETA);
EXPECT_NE(Feature::IS_AVAILABLE,
feature
->IsAvailableToManifest(kId1, Manifest::TYPE_EXTENSION,
ManifestLocation::kInvalidLocation,
Feature::UNSPECIFIED_PLATFORM,
kUnspecifiedContextId)
.result());
}
}
TEST(SimpleFeatureUnitTest, TestRequiresDelegatedAvailabilityCheck) {
// Test a feature that requires a delegated availability check, but the check
// fails.
std::string expected_feature_name = "DisallowedFeature";
uint32_t delegated_availability_check_call_count = 0;
auto delegated_availability_check = base::BindLambdaForTesting(
[&](const std::string& api_full_name, const Extension* extension,
mojom::ContextType context, const GURL& url,
Feature::Platform platform, int context_id, bool check_developer_mode,
const ContextData& context_data) {
++delegated_availability_check_call_count;
EXPECT_EQ(expected_feature_name, api_full_name);
return api_full_name == "AllowedFeature";
});
SimpleFeature feature;
feature.set_requires_delegated_availability_check(true);
feature.set_contexts({mojom::ContextType::kWebPage});
const GURL kTestPage = GURL("https://www.example.com");
feature.set_matches({kTestPage.spec().c_str()});
{
// Test a feature that requires a delegated availability check but is
// missing the check handler.
EXPECT_EQ(Feature::MISSING_DELEGATED_AVAILABILITY_CHECK,
feature
.IsAvailableToContext(
/*extension=*/nullptr, mojom::ContextType::kWebPage,
kTestPage, kUnspecifiedContextId, TestContextData())
.result());
}
feature.SetDelegatedAvailabilityCheckHandler(delegated_availability_check);
feature.set_name(expected_feature_name);
{
// Test a feature that requires a delegated availability check and the check
// is not successful.
EXPECT_EQ(Feature::FAILED_DELEGATED_AVAILABILITY_CHECK,
feature
.IsAvailableToContext(
/*extension=*/nullptr, mojom::ContextType::kWebPage,
kTestPage, kUnspecifiedContextId, TestContextData())
.result());
EXPECT_EQ(1u, delegated_availability_check_call_count);
}
expected_feature_name = "AllowedFeature";
feature.set_name(expected_feature_name);
{
// Test a feature that requires a delegated availability check and the check
// is successful.
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToContext(
/*extension=*/nullptr, mojom::ContextType::kWebPage,
kTestPage, kUnspecifiedContextId, TestContextData())
.result());
EXPECT_EQ(2u, delegated_availability_check_call_count);
}
feature.set_channel(version_info::Channel::DEV);
{
// Test a feature that requires a delegated availability check and the check
// would be successful, but actually isn't called since the environment
// check fails.
ScopedCurrentChannel current_channel(Channel::STABLE);
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
feature
.IsAvailableToContext(
/*extension=*/nullptr, mojom::ContextType::kWebPage,
kTestPage, kUnspecifiedContextId, TestContextData())
.result());
EXPECT_EQ(2u, delegated_availability_check_call_count);
}
feature.set_channel(version_info::Channel::STABLE);
{
// Test a feature that requires a delegated availability check and the check
// would be successful, then confirm the check is called because the
// environment check passes.
ScopedCurrentChannel current_channel(Channel::STABLE);
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToContext(
/*extension=*/nullptr, mojom::ContextType::kWebPage,
kTestPage, kUnspecifiedContextId, TestContextData())
.result());
EXPECT_EQ(3u, delegated_availability_check_call_count);
}
const GURL kTestPageNotInMatchList = GURL("https://www.not.example.com");
{
// Test a feature that requires a delegated availability check and the check
// would be successful, but the URL is not contained in the matchlist.
EXPECT_EQ(Feature::INVALID_URL,
feature
.IsAvailableToContext(
/*extension=*/nullptr, mojom::ContextType::kWebPage,
kTestPageNotInMatchList, kUnspecifiedContextId,
TestContextData())
.result());
EXPECT_EQ(4u, delegated_availability_check_call_count);
}
}
TEST(SimpleFeatureUnitTest, TestChannelsWithoutExtension) {
// Create a webui feature available on trunk.
SimpleFeature feature;
feature.set_contexts({mojom::ContextType::kWebUi});
feature.set_matches({content::GetWebUIURLString("settings/*").c_str()});
feature.set_channel(version_info::Channel::UNKNOWN);
const GURL kAllowlistedUrl(content::GetWebUIURL("settings/foo"));
const GURL kOtherUrl("https://example.com");
{
// It should be available on trunk.
ScopedCurrentChannel current_channel(Channel::UNKNOWN);
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToContext(nullptr, mojom::ContextType::kWebUi,
kAllowlistedUrl, kUnspecifiedContextId,
TestContextData())
.result());
}
{
// It should be unavailable on beta.
ScopedCurrentChannel current_channel(Channel::BETA);
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL,
feature
.IsAvailableToContext(nullptr, mojom::ContextType::kWebUi,
kAllowlistedUrl, kUnspecifiedContextId,
TestContextData())
.result());
}
}
TEST(SimpleFeatureUnitTest, TestAvailableToEnvironment) {
{
// Test with no environment restrictions, but with other restrictions. The
// result should always be available.
SimpleFeature feature;
feature.set_min_manifest_version(2);
feature.set_extension_types({Manifest::TYPE_EXTENSION});
feature.set_contexts({mojom::ContextType::kPrivilegedExtension});
EXPECT_EQ(Feature::IS_AVAILABLE,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
// Test with channel restrictions.
SimpleFeature feature;
feature.set_channel(Channel::BETA);
{
ScopedCurrentChannel current_channel(Channel::BETA);
EXPECT_EQ(
Feature::IS_AVAILABLE,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
{
ScopedCurrentChannel current_channel(Channel::STABLE);
EXPECT_EQ(
Feature::UNSUPPORTED_CHANNEL,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
}
{
// Test with command-line restrictions.
const char kFakeSwitch[] = "some-fake-switch";
SimpleFeature feature;
feature.set_command_line_switch(kFakeSwitch);
EXPECT_EQ(Feature::MISSING_COMMAND_LINE_SWITCH,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
{
base::test::ScopedCommandLine command_line;
command_line.GetProcessCommandLine()->AppendSwitch(
base::StringPrintf("enable-%s", kFakeSwitch));
EXPECT_EQ(
Feature::IS_AVAILABLE,
feature.IsAvailableToEnvironment(kUnspecifiedContextId).result());
}
}
// Note: if we wanted, we could add a ScopedCurrentPlatform() and add
// platform-test restrictions?
}
TEST(SimpleFeatureUnitTest, TestExperimentalExtensionApisSwitch) {
ScopedCurrentChannel current_channel(Channel::STABLE);
auto test_feature = []() {
SimpleFeature feature;
feature.set_channel(version_info::Channel::UNKNOWN);
return feature.IsAvailableToEnvironment(kUnspecifiedContextId).result();
};
{
base::test::ScopedCommandLine scoped_command_line;
EXPECT_EQ(Feature::UNSUPPORTED_CHANNEL, test_feature());
}
{
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitch(
switches::kEnableExperimentalExtensionApis);
EXPECT_EQ(Feature::IS_AVAILABLE, test_feature());
}
}
TEST_F(SimpleFeatureTest, RestrictDeveloperModeAPIs) {
constexpr int kContextId1 = 1;
constexpr int kContextId2 = 2;
SimpleFeature dev_mode_only_feature;
dev_mode_only_feature.set_developer_mode_only(true);
SimpleFeature other_feature;
// With kDeveloperModeRestriction enabled, developer mode-only APIs
// should be available if and only if the user is in dev mode.
SetCurrentDeveloperMode(kContextId1, true);
EXPECT_EQ(
Feature::IS_AVAILABLE,
dev_mode_only_feature.IsAvailableToEnvironment(kContextId1).result());
EXPECT_EQ(Feature::IS_AVAILABLE,
other_feature.IsAvailableToEnvironment(kContextId1).result());
SetCurrentDeveloperMode(kContextId1, false);
EXPECT_EQ(
Feature::REQUIRES_DEVELOPER_MODE,
dev_mode_only_feature.IsAvailableToEnvironment(kContextId1).result());
EXPECT_EQ(Feature::IS_AVAILABLE,
other_feature.IsAvailableToEnvironment(kContextId1).result());
SetCurrentDeveloperMode(kContextId2, true);
EXPECT_EQ(
Feature::IS_AVAILABLE,
dev_mode_only_feature.IsAvailableToEnvironment(kContextId2).result());
EXPECT_EQ(Feature::IS_AVAILABLE,
other_feature.IsAvailableToEnvironment(kContextId2).result());
SetCurrentDeveloperMode(kContextId2, false);
EXPECT_EQ(
Feature::REQUIRES_DEVELOPER_MODE,
dev_mode_only_feature.IsAvailableToEnvironment(kContextId2).result());
EXPECT_EQ(Feature::IS_AVAILABLE,
other_feature.IsAvailableToEnvironment(kContextId2).result());
}
TEST(SimpleFeatureUnitTest, DisallowForServiceWorkers) {
SimpleFeature feature;
feature.set_name("somefeature");
feature.set_contexts({mojom::ContextType::kPrivilegedExtension});
feature.set_extension_types({Manifest::TYPE_EXTENSION});
auto extension = ExtensionBuilder("test")
.SetBackgroundContext(
ExtensionBuilder::BackgroundContext::SERVICE_WORKER)
.Build();
ASSERT_TRUE(extension.get());
EXPECT_TRUE(BackgroundInfo::IsServiceWorkerBased(extension.get()));
// Expect the feature is allowed, since the default is to allow.
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
extension->GetResourceURL(
ExtensionBuilder::kServiceWorkerScriptFile),
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId,
TestContextData())
.result());
// Check with a different script file, which should return available,
// since it's not a service worker context.
EXPECT_EQ(Feature::IS_AVAILABLE,
feature
.IsAvailableToContext(extension.get(),
mojom::ContextType::kPrivilegedExtension,
extension->GetResourceURL("other.js"),
Feature::CHROMEOS_PLATFORM,
kUnspecifiedContextId, TestContextData())
.result());
// Disable the feature for service workers. The feature should be disallowed.
feature.set_disallow_for_service_workers(true);
EXPECT_EQ(Feature::INVALID_CONTEXT,
feature
.IsAvailableToContext(
extension.get(), mojom::ContextType::kPrivilegedExtension,
extension->GetResourceURL(
ExtensionBuilder::kServiceWorkerScriptFile),
Feature::CHROMEOS_PLATFORM, kUnspecifiedContextId,
TestContextData())
.result());
}
} // namespace extensions