| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/feature_policy/feature_policy_parser.h" |
| |
| #include <map> |
| #include <string> |
| |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/feature_policy/feature_policy.mojom-blink.h" |
| #include "third_party/blink/renderer/core/frame/local_dom_window.h" |
| #include "third_party/blink/renderer/core/frame/settings.h" |
| #include "third_party/blink/renderer/core/loader/empty_clients.h" |
| #include "third_party/blink/renderer/core/testing/page_test_base.h" |
| #include "third_party/blink/renderer/platform/testing/histogram_tester.h" |
| |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| // Origin strings used for tests |
| #define ORIGIN_A "https://example.com/" |
| #define ORIGIN_B "https://example.net/" |
| #define ORIGIN_C "https://example.org/" |
| #define OPAQUE_ORIGIN "" |
| |
| // identifier used for feature/permissions policy parsing test. |
| // when there is a testcase for one syntax but not the other. |
| #define NOT_APPLICABLE nullptr |
| |
| class GURL; |
| |
| namespace blink { |
| |
| namespace { |
| |
| const char* const kValidHeaderPolicies[] = { |
| "", // An empty policy. |
| " ", // An empty policy. |
| ";;", // Empty policies. |
| ",,", // Empty policies. |
| " ; ;", // Empty policies. |
| " , ,", // Empty policies. |
| ",;,", // Empty policies. |
| "geolocation 'none'", |
| "geolocation 'self'", |
| "geolocation", |
| "geolocation; fullscreen; payment", |
| "geolocation *", |
| "geolocation " ORIGIN_A "", |
| "geolocation " ORIGIN_B "", |
| "geolocation " ORIGIN_A " " ORIGIN_B "", |
| "geolocation 'none' " ORIGIN_A " " ORIGIN_B "", |
| "geolocation " ORIGIN_A " 'none' " ORIGIN_B "", |
| "geolocation 'none' 'none' 'none'", |
| "geolocation " ORIGIN_A " *", |
| "fullscreen " ORIGIN_A "; payment 'self'", |
| "fullscreen " ORIGIN_A "; payment *, geolocation 'self'"}; |
| |
| const char* const kInvalidHeaderPolicies[] = { |
| "badfeaturename", |
| "badfeaturename 'self'", |
| "1.0", |
| "geolocation 'src'", // Only valid for iframe allow attribute. |
| "geolocation data://badorigin", |
| "geolocation https://bad;origin", |
| "geolocation https:/bad,origin", |
| "geolocation https://example.com, https://a.com", |
| "geolocation *, payment data://badorigin", |
| "geolocation ws://xn--fd\xbcwsw3taaaaaBaa333aBBBBBBJBBJBBBt", |
| }; |
| |
| // Names of UMA histograms |
| const char kAllowlistAttributeHistogram[] = |
| "Blink.UseCounter.FeaturePolicy.AttributeAllowlistType"; |
| const char kAllowlistHeaderHistogram[] = |
| "Blink.UseCounter.FeaturePolicy.HeaderAllowlistType"; |
| |
| } // namespace |
| |
| class FeaturePolicyParserTest : public ::testing::Test { |
| protected: |
| FeaturePolicyParserTest() = default; |
| |
| ~FeaturePolicyParserTest() override = default; |
| |
| scoped_refptr<const SecurityOrigin> origin_a_ = |
| SecurityOrigin::CreateFromString(ORIGIN_A); |
| scoped_refptr<const SecurityOrigin> origin_b_ = |
| SecurityOrigin::CreateFromString(ORIGIN_B); |
| scoped_refptr<const SecurityOrigin> origin_c_ = |
| SecurityOrigin::CreateFromString(ORIGIN_C); |
| |
| url::Origin expected_url_origin_a_ = url::Origin::Create(GURL(ORIGIN_A)); |
| url::Origin expected_url_origin_b_ = url::Origin::Create(GURL(ORIGIN_B)); |
| url::Origin expected_url_origin_c_ = url::Origin::Create(GURL(ORIGIN_C)); |
| |
| const FeatureNameMap test_feature_name_map = { |
| {"fullscreen", blink::mojom::blink::FeaturePolicyFeature::kFullscreen}, |
| {"payment", blink::mojom::blink::FeaturePolicyFeature::kPayment}, |
| {"geolocation", blink::mojom::blink::FeaturePolicyFeature::kGeolocation}}; |
| |
| ParsedFeaturePolicy ParseFeaturePolicyHeader( |
| const String& feature_policy_header, |
| scoped_refptr<const SecurityOrigin> origin, |
| PolicyParserMessageBuffer& logger, |
| ExecutionContext* context = nullptr) { |
| return FeaturePolicyParser::ParseHeader( |
| feature_policy_header, g_empty_string, origin, logger, logger, context); |
| } |
| }; |
| |
| struct ParsedPolicyDeclarationForTest { |
| mojom::blink::FeaturePolicyFeature feature; |
| bool matches_all_origins; |
| bool matches_opaque_src; |
| std::vector<const char*> origins; |
| }; |
| |
| using ParsedPolicyForTest = std::vector<ParsedPolicyDeclarationForTest>; |
| |
| struct FeaturePolicyParserTestCase { |
| const char* test_name; |
| |
| // Test inputs. |
| const char* feature_policy_string; |
| const char* permissions_policy_string; |
| const char* self_origin; |
| const char* src_origin; |
| |
| // Test expectation. |
| ParsedPolicyForTest expected_parse_result; |
| }; |
| |
| class FeaturePolicyParserParsingTest |
| : public FeaturePolicyParserTest, |
| public ::testing::WithParamInterface<FeaturePolicyParserTestCase> { |
| private: |
| scoped_refptr<const SecurityOrigin> GetSrcOrigin(const char* origin_str) { |
| scoped_refptr<const SecurityOrigin> src_origin; |
| if (String(origin_str) == OPAQUE_ORIGIN) { |
| src_origin = SecurityOrigin::CreateUniqueOpaque(); |
| } else { |
| src_origin = |
| origin_str ? SecurityOrigin::CreateFromString(origin_str) : nullptr; |
| } |
| return src_origin; |
| } |
| |
| protected: |
| ParsedFeaturePolicy ParseFeaturePolicy(const char* policy_string, |
| const char* self_origin_string, |
| const char* src_origin_string, |
| PolicyParserMessageBuffer& logger, |
| const FeatureNameMap& feature_names, |
| ExecutionContext* context = nullptr) { |
| return FeaturePolicyParser::ParseFeaturePolicyForTest( |
| policy_string, SecurityOrigin::CreateFromString(self_origin_string), |
| GetSrcOrigin(src_origin_string), logger, feature_names, context); |
| } |
| |
| ParsedFeaturePolicy ParsePermissionsPolicy( |
| const char* policy_string, |
| const char* self_origin_string, |
| const char* src_origin_string, |
| PolicyParserMessageBuffer& logger, |
| const FeatureNameMap& feature_names, |
| ExecutionContext* context = nullptr) { |
| return FeaturePolicyParser::ParsePermissionsPolicyForTest( |
| policy_string, SecurityOrigin::CreateFromString(self_origin_string), |
| GetSrcOrigin(src_origin_string), logger, feature_names, context); |
| } |
| |
| void CheckParsedPolicy(const ParsedFeaturePolicy& actual, |
| const ParsedPolicyForTest& expected) { |
| ASSERT_EQ(actual.size(), expected.size()); |
| for (size_t i = 0; i < actual.size(); ++i) { |
| const auto& actual_declaration = actual[i]; |
| const auto& expected_declaration = expected[i]; |
| |
| EXPECT_EQ(actual_declaration.feature, expected_declaration.feature); |
| EXPECT_EQ(actual_declaration.matches_all_origins, |
| expected_declaration.matches_all_origins); |
| EXPECT_EQ(actual_declaration.matches_opaque_src, |
| expected_declaration.matches_opaque_src); |
| |
| ASSERT_EQ(actual_declaration.allowed_origins.size(), |
| expected_declaration.origins.size()); |
| for (size_t j = 0; j < actual_declaration.allowed_origins.size(); ++j) { |
| EXPECT_TRUE(actual_declaration.allowed_origins[j].IsSameOriginWith( |
| url::Origin::Create(GURL(expected_declaration.origins[j])))); |
| } |
| } |
| } |
| |
| void CheckConsoleMessage( |
| const Vector<PolicyParserMessageBuffer::Message>& actual, |
| const std::vector<String> expected) { |
| ASSERT_EQ(actual.size(), expected.size()); |
| for (size_t i = 0; i < actual.size(); ++i) { |
| EXPECT_EQ(actual[i].content, expected[i]); |
| } |
| } |
| |
| public: |
| static const FeaturePolicyParserTestCase kCases[]; |
| }; |
| |
| const FeaturePolicyParserTestCase FeaturePolicyParserParsingTest::kCases[] = { |
| { |
| /* test_name */ "EmptyPolicy", |
| /* feature_policy_string */ "", |
| /* permissions_policy_string */ "", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ ORIGIN_B, |
| /* expected_parse_result */ {}, |
| }, |
| { |
| /* test_name */ "SimplePolicyWithSelf", |
| /* feature_policy_string */ "geolocation 'self'", |
| /* permissions_policy_string */ "geolocation=self", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ ORIGIN_B, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "SimplePolicyWithSelfExplicitListSyntax", |
| /* feature_policy_string */ NOT_APPLICABLE, |
| /* permissions_policy_string */ "geolocation=(self)", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ ORIGIN_B, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "SimplePolicyWithStar", |
| /* feature_policy_string */ "geolocation *", |
| /* permissions_policy_string */ "geolocation=*", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ ORIGIN_B, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ true, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "ComplicatedPolicy", |
| /* feature_policy_string */ |
| "geolocation *; " |
| "fullscreen " ORIGIN_B " " ORIGIN_C "; " |
| "payment 'self'", |
| /* permissions_policy_string */ |
| "geolocation=*, " |
| "fullscreen=(\"" ORIGIN_B "\" \"" ORIGIN_C "\")," |
| "payment=self", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ ORIGIN_B, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ true, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kFullscreen, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_B, ORIGIN_C}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kPayment, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "MultiplePoliciesIncludingBadFeatureName", |
| /* feature_policy_string */ |
| "geolocation * " ORIGIN_B "; " |
| "fullscreen " ORIGIN_B " bad_feature_name " ORIGIN_C ";" |
| "payment 'self' badorigin", |
| /* permissions_policy_string */ |
| "geolocation=(* \"" ORIGIN_B "\")," |
| "fullscreen=(\"" ORIGIN_B "\" bad_feature_name \"" ORIGIN_C "\")," |
| "payment=(self \"badorigin\")", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ ORIGIN_B, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ true, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kFullscreen, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_B, ORIGIN_C}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kPayment, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "HeaderPoliciesWithNoOptionalOriginLists", |
| /* feature_policy_string */ "geolocation;fullscreen;payment", |
| // Note: In structured header, if no value is associated with a key |
| // in dictionary, default value would be boolean true, which is |
| // not allowed as allowlist value in permission policy syntax. |
| /* permissions_policy_string */ |
| "geolocation=self,fullscreen=self,payment=self", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ nullptr, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kFullscreen, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kPayment, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "EmptyPolicyOpaqueSrcOrigin", |
| /* feature_policy_string */ "", |
| /* permissions_policy_string */ "", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ OPAQUE_ORIGIN, |
| /* expected_parse_result */ {}, |
| }, |
| { |
| /* test_name */ "SimplePolicyOpaqueSrcOrigin", |
| /* feature_policy_string */ "geolocation", |
| /* permissions_policy_string */ NOT_APPLICABLE, |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ OPAQUE_ORIGIN, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "SimplePolicyWithSrcOpaqueSrcOrigin", |
| /* feature_policy_string */ "geolocation 'src'", |
| /* permissions_policy_string */ NOT_APPLICABLE, |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ OPAQUE_ORIGIN, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "SimplePolicyWithStarOpaqueSrcOrigin", |
| /* feature_policy_string */ "geolocation *", |
| /* permissions_policy_string */ "geolocation=*", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ OPAQUE_ORIGIN, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ true, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "PolicyWithExplicitOriginsOpaqueSrcOrigin", |
| /* feature_policy_string */ "geolocation " ORIGIN_B " " ORIGIN_C, |
| /* permissions_policy_string */ |
| "geolocation=(\"" ORIGIN_B "\" \"" ORIGIN_C "\")", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ OPAQUE_ORIGIN, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_B, ORIGIN_C}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "PolicyWithMultipleOriginsIncludingSrc" |
| "OpaqueSrcOrigin", |
| /* feature_policy_string */ "geolocation " ORIGIN_B " 'src'", |
| /* permissions_policy_string */ NOT_APPLICABLE, |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ OPAQUE_ORIGIN, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ true, |
| {ORIGIN_B}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "PolicyWithInvalidDataTypeInt", |
| /* feature_policy_string */ NOT_APPLICABLE, |
| // int value should be rejected as allowlist items. |
| /* permissions_policy_string */ "geolocation=9", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ nullptr, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "PolicyWithInvalidDataTypeFloat", |
| /* feature_policy_string */ NOT_APPLICABLE, |
| // decimal value should be rejected as allowlist items. |
| /* permissions_policy_string */ "geolocation=1.1", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ nullptr, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "PolicyWithInvalidDataTypeBoolean", |
| /* feature_policy_string */ NOT_APPLICABLE, |
| // boolean value should be rejected as allowlist items. |
| /* permissions_policy_string */ "geolocation=?0", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ nullptr, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {}, |
| }, |
| }, |
| }, |
| { |
| /* test_name */ "PolicyWithEmptyOriginString", |
| /* feature_policy_string */ NOT_APPLICABLE, |
| /* permissions_policy_string */ "geolocation=\"\"", |
| /* self_origin */ ORIGIN_A, |
| /* src_origin */ nullptr, |
| /* expected_parse_result */ |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {}, |
| }, |
| }, |
| }, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| FeaturePolicyParserParsingTest, |
| ::testing::ValuesIn(FeaturePolicyParserParsingTest::kCases), |
| [](const testing::TestParamInfo<FeaturePolicyParserTestCase>& param_info) { |
| return param_info.param.test_name; |
| }); |
| |
| TEST_P(FeaturePolicyParserParsingTest, FeaturePolicyParsedCorrectly) { |
| PolicyParserMessageBuffer logger; |
| const FeaturePolicyParserTestCase& test_case = GetParam(); |
| if (test_case.feature_policy_string == NOT_APPLICABLE) |
| return; |
| |
| ASSERT_NE(test_case.self_origin, nullptr); |
| |
| CheckParsedPolicy( |
| ParseFeaturePolicy(test_case.feature_policy_string, test_case.self_origin, |
| test_case.src_origin, logger, test_feature_name_map), |
| test_case.expected_parse_result); |
| } |
| |
| TEST_P(FeaturePolicyParserParsingTest, PermissionsPolicyParsedCorrectly) { |
| PolicyParserMessageBuffer logger; |
| const FeaturePolicyParserTestCase& test_case = GetParam(); |
| if (test_case.permissions_policy_string == NOT_APPLICABLE) |
| return; |
| |
| ASSERT_NE(test_case.self_origin, nullptr); |
| CheckParsedPolicy( |
| ParsePermissionsPolicy(test_case.permissions_policy_string, |
| test_case.self_origin, test_case.src_origin, |
| logger, test_feature_name_map), |
| test_case.expected_parse_result); |
| } |
| |
| TEST_F(FeaturePolicyParserParsingTest, |
| FeaturePolicyDuplicatedFeatureDeclaration) { |
| PolicyParserMessageBuffer logger; |
| |
| // For Feature-Policy header, if there are multiple declaration for same |
| // feature, the allowlist value from *FIRST* declaration will be taken. |
| CheckParsedPolicy(FeaturePolicyParser::ParseHeader( |
| "geolocation 'none', geolocation 'self'", "", |
| origin_a_.get(), logger, logger, nullptr /* context */), |
| { |
| { |
| // allowlist value 'none' is expected. |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {}, |
| }, |
| }); |
| |
| EXPECT_TRUE(logger.GetMessages().IsEmpty()); |
| } |
| |
| TEST_F(FeaturePolicyParserParsingTest, |
| PermissionsPolicyDuplicatedFeatureDeclaration) { |
| PolicyParserMessageBuffer logger; |
| |
| // For Permissions-Policy header, if there are multiple declaration for same |
| // feature, the allowlist value from *LAST* declaration will be taken. |
| CheckParsedPolicy(FeaturePolicyParser::ParseHeader( |
| "", "geolocation=(), geolocation=self", origin_a_.get(), |
| logger, logger, nullptr /* context */), |
| { |
| { |
| // allowlist value 'self' is expected. |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }); |
| |
| EXPECT_TRUE(logger.GetMessages().IsEmpty()); |
| } |
| |
| TEST_F(FeaturePolicyParserParsingTest, |
| FeaturePolicyHeaderPermissionsPolicyHeaderCoExistConflictEntry) { |
| PolicyParserMessageBuffer logger; |
| |
| // When there is conflict take the value from permission policy, |
| // non-conflicting entries will be merged. |
| CheckParsedPolicy(FeaturePolicyParser::ParseHeader( |
| "geolocation 'none', fullscreen 'self'", |
| "geolocation=self, payment=*", origin_a_.get(), logger, |
| logger, nullptr /* context */), |
| { |
| { |
| // With geolocation appearing in both headers, |
| // the value should be taken from permissions policy |
| // header, which is 'self' here. |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kPayment, |
| /* matches_all_origins */ true, |
| /* matches_opaque_src */ true, |
| {}, |
| }, |
| { |
| mojom::blink::FeaturePolicyFeature::kFullscreen, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }); |
| } |
| |
| TEST_F(FeaturePolicyParserParsingTest, |
| FeaturePolicyHeaderPermissionsPolicyHeaderCoExistSeparateLogger) { |
| PolicyParserMessageBuffer feature_policy_logger("Feature Policy: "); |
| PolicyParserMessageBuffer permissions_policy_logger("Permissions Policy: "); |
| |
| // 'geolocation' in permissions policy has a invalid allowlist item, which |
| // results in an empty allowlist, which is equivalent to 'none' in feature |
| // policy syntax. |
| CheckParsedPolicy( |
| FeaturePolicyParser::ParseHeader( |
| "worse-feature 'none', geolocation 'self'" /* feature_policy_header */ |
| , |
| "bad-feature=*, geolocation=\"data://bad-origin\"" /* permissions_policy_header |
| */ |
| , |
| origin_a_.get(), feature_policy_logger, permissions_policy_logger, |
| nullptr /* context */ |
| ), |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {}, |
| }, |
| }); |
| |
| CheckConsoleMessage( |
| feature_policy_logger.GetMessages(), |
| { |
| "Feature Policy: Unrecognized feature: 'worse-feature'.", |
| "Feature Policy: Feature geolocation has been specified in both " |
| "Feature-Policy and Permissions-Policy header. Value defined in " |
| "Permissions-Policy header will be used.", |
| }); |
| CheckConsoleMessage( |
| permissions_policy_logger.GetMessages(), |
| { |
| "Permissions Policy: Unrecognized feature: 'bad-feature'.", |
| "Permissions Policy: Unrecognized origin: 'data://bad-origin'.", |
| }); |
| } |
| |
| TEST_F(FeaturePolicyParserParsingTest, CommaSeparatorInAttribute) { |
| PolicyParserMessageBuffer logger; |
| |
| CheckParsedPolicy( |
| FeaturePolicyParser::ParseAttribute( |
| "geolocation 'none', fullscreen 'self'", |
| /* self_origin */ origin_a_.get(), |
| /* src_origin */ origin_a_.get(), logger, /* context */ nullptr), |
| { |
| { |
| mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* matches_all_origins */ false, |
| /* matches_opaque_src */ false, |
| {ORIGIN_A}, |
| }, |
| }); |
| |
| EXPECT_EQ(logger.GetMessages().size(), 2u) |
| << "Parser should report parsing error."; |
| |
| EXPECT_EQ(logger.GetMessages().front().content.Ascii(), |
| "Unrecognized origin: ''none','.") |
| << "\"'none',\" should be treated as an invalid allowlist item "; |
| |
| EXPECT_EQ(logger.GetMessages().back().content.Ascii(), |
| "Unrecognized origin: 'fullscreen'.") |
| << "\"fullscreen\" should be treated as an invalid allowlist item"; |
| } |
| |
| TEST_F(FeaturePolicyParserTest, ParseValidHeaderPolicy) { |
| for (const char* policy_string : kValidHeaderPolicies) { |
| PolicyParserMessageBuffer logger; |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| policy_string, origin_a_.get(), nullptr, logger, test_feature_name_map); |
| EXPECT_EQ(0UL, logger.GetMessages().size()) |
| << "Should parse " << policy_string; |
| } |
| } |
| |
| TEST_F(FeaturePolicyParserTest, ParseInvalidHeaderPolicy) { |
| for (const char* policy_string : kInvalidHeaderPolicies) { |
| PolicyParserMessageBuffer logger; |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| policy_string, origin_a_.get(), nullptr, logger, test_feature_name_map); |
| EXPECT_LT(0UL, logger.GetMessages().size()) |
| << "Should fail to parse " << policy_string; |
| } |
| } |
| |
| TEST_F(FeaturePolicyParserTest, ParseTooLongPolicy) { |
| PolicyParserMessageBuffer logger; |
| auto policy_string = "geolocation http://" + std::string(1 << 17, 'a'); |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| policy_string.c_str(), origin_a_.get(), origin_b_.get(), logger, |
| test_feature_name_map); |
| EXPECT_EQ(1UL, logger.GetMessages().size()) |
| << "Should fail to parse feature policy string with size " |
| << policy_string.size(); |
| FeaturePolicyParser::ParsePermissionsPolicyForTest( |
| policy_string.c_str(), origin_a_.get(), origin_b_.get(), logger, |
| test_feature_name_map); |
| EXPECT_EQ(2UL, logger.GetMessages().size()) |
| << "Should fail to parse permissions policy string with size " |
| << policy_string.size(); |
| } |
| |
| // Test histogram counting the use of feature policies in header. |
| TEST_F(FeaturePolicyParserTest, HeaderHistogram) { |
| const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Header"; |
| HistogramTester tester; |
| PolicyParserMessageBuffer logger; |
| |
| FeaturePolicyParser::ParseFeaturePolicyForTest("payment; fullscreen", |
| origin_a_.get(), nullptr, |
| logger, test_feature_name_map); |
| tester.ExpectTotalCount(histogram_name, 2); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kPayment), 1); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kFullscreen), |
| 1); |
| } |
| |
| // Test counting the use of each feature policy only once per header. |
| TEST_F(FeaturePolicyParserTest, HistogramMultiple) { |
| const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Header"; |
| HistogramTester tester; |
| PolicyParserMessageBuffer logger; |
| |
| // If the same feature is listed multiple times, it should only be counted |
| // once. |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| "geolocation 'self'; payment; geolocation *", origin_a_.get(), nullptr, |
| logger, test_feature_name_map); |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| "fullscreen 'self', fullscreen *", origin_a_.get(), nullptr, logger, |
| test_feature_name_map); |
| tester.ExpectTotalCount(histogram_name, 3); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kGeolocation), |
| 1); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kFullscreen), |
| 1); |
| } |
| |
| // Test histogram counting the use of feature policies via "allow" |
| // attribute. This test parses two policies on the same document. |
| TEST_F(FeaturePolicyParserTest, AllowHistogramSameDocument) { |
| const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Allow"; |
| HistogramTester tester; |
| PolicyParserMessageBuffer logger; |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| "payment; fullscreen", origin_a_.get(), origin_b_.get(), logger, |
| test_feature_name_map, dummy->GetFrame().DomWindow()); |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| "fullscreen; geolocation", origin_a_.get(), origin_b_.get(), logger, |
| test_feature_name_map, dummy->GetFrame().DomWindow()); |
| |
| tester.ExpectTotalCount(histogram_name, 3); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kPayment), 1); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kFullscreen), |
| 1); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kGeolocation), |
| 1); |
| } |
| |
| // Test histogram counting the use of feature policies via "allow" |
| // attribute. This test parses two policies on different documents. |
| TEST_F(FeaturePolicyParserTest, AllowHistogramDifferentDocument) { |
| const char* histogram_name = "Blink.UseCounter.FeaturePolicy.Allow"; |
| HistogramTester tester; |
| PolicyParserMessageBuffer logger; |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| auto dummy2 = std::make_unique<DummyPageHolder>(); |
| |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| "payment; fullscreen", origin_a_.get(), origin_b_.get(), logger, |
| test_feature_name_map, dummy->GetFrame().DomWindow()); |
| FeaturePolicyParser::ParseFeaturePolicyForTest( |
| "fullscreen; geolocation", origin_a_.get(), origin_b_.get(), logger, |
| test_feature_name_map, dummy2->GetFrame().DomWindow()); |
| |
| tester.ExpectTotalCount(histogram_name, 4); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kPayment), 1); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kFullscreen), |
| 2); |
| tester.ExpectBucketCount( |
| histogram_name, |
| static_cast<int>(blink::mojom::blink::FeaturePolicyFeature::kGeolocation), |
| 1); |
| } |
| |
| // Tests the use counter for comma separator in declarations. |
| TEST_F(FeaturePolicyParserTest, CommaSeparatedUseCounter) { |
| PolicyParserMessageBuffer logger; |
| |
| // Declarations without a semicolon should not trigger the use counter. |
| { |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| ParseFeaturePolicyHeader("payment", origin_a_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| EXPECT_FALSE(dummy->GetDocument().IsUseCounted( |
| WebFeature::kFeaturePolicyCommaSeparatedDeclarations)); |
| } |
| |
| // Validate that declarations which should trigger the use counter do. |
| { |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| ParseFeaturePolicyHeader("payment, fullscreen", origin_a_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| EXPECT_TRUE(dummy->GetDocument().IsUseCounted( |
| WebFeature::kFeaturePolicyCommaSeparatedDeclarations)) |
| << "'payment, fullscreen' should trigger the comma separated use " |
| "counter."; |
| } |
| } |
| |
| // Tests the use counter for semicolon separator in declarations. |
| TEST_F(FeaturePolicyParserTest, SemicolonSeparatedUseCounter) { |
| PolicyParserMessageBuffer logger; |
| |
| // Declarations without a semicolon should not trigger the use counter. |
| { |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| ParseFeaturePolicyHeader("payment", origin_a_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| EXPECT_FALSE(dummy->GetDocument().IsUseCounted( |
| WebFeature::kFeaturePolicySemicolonSeparatedDeclarations)); |
| } |
| |
| // Validate that declarations which should trigger the use counter do. |
| { |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| ParseFeaturePolicyHeader("payment; fullscreen", origin_a_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| EXPECT_TRUE(dummy->GetDocument().IsUseCounted( |
| WebFeature::kFeaturePolicySemicolonSeparatedDeclarations)) |
| << "'payment; fullscreen' should trigger the semicolon separated use " |
| "counter."; |
| } |
| } |
| |
| // Tests that the histograms for usage of various directive options are |
| // recorded correctly. |
| struct AllowlistHistogramData { |
| // Name of the test |
| const char* name; |
| const char* policy_declaration; |
| int expected_total; |
| std::vector<FeaturePolicyAllowlistType> expected_buckets; |
| }; |
| |
| class FeaturePolicyAllowlistHistogramTest |
| : public FeaturePolicyParserTest, |
| public testing::WithParamInterface<AllowlistHistogramData> { |
| public: |
| static const AllowlistHistogramData kCases[]; |
| }; |
| |
| const AllowlistHistogramData FeaturePolicyAllowlistHistogramTest::kCases[] = { |
| {"Empty", "fullscreen", 1, {FeaturePolicyAllowlistType::kEmpty}}, |
| {"Empty_MultipleDirectivesSemicolon", |
| "fullscreen; payment", |
| 1, |
| {FeaturePolicyAllowlistType::kEmpty}}, |
| {"Star", "fullscreen *", 1, {FeaturePolicyAllowlistType::kStar}}, |
| {"Star_MultipleDirectivesSemicolon", |
| "fullscreen *; payment *", |
| 1, |
| {FeaturePolicyAllowlistType::kStar}}, |
| {"Self", "fullscreen 'self'", 1, {FeaturePolicyAllowlistType::kSelf}}, |
| {"Self_MultipleDirectives", |
| "fullscreen 'self'; geolocation 'self'; payment 'self'", |
| 1, |
| {FeaturePolicyAllowlistType::kSelf}}, |
| {"None", "fullscreen 'none'", 1, {FeaturePolicyAllowlistType::kNone}}, |
| {"None_MultipleDirectives", |
| "fullscreen 'none'; payment 'none'", |
| 1, |
| {FeaturePolicyAllowlistType::kNone}}, |
| {"Origins", |
| "fullscreen " ORIGIN_A, |
| 1, |
| {FeaturePolicyAllowlistType::kOrigins}}, |
| {"Origins_MultipleDirectivesSemicolon", |
| "fullscreen " ORIGIN_A "; payment " ORIGIN_A " " ORIGIN_B, |
| 1, |
| {FeaturePolicyAllowlistType::kOrigins}}, |
| {"Mixed", |
| "fullscreen 'self' " ORIGIN_A, |
| 1, |
| {FeaturePolicyAllowlistType::kMixed}}, |
| {"Mixed_MultipleDirectives", |
| "fullscreen 'self' " ORIGIN_A "; payment 'none' " ORIGIN_A " " ORIGIN_B, |
| 1, |
| {FeaturePolicyAllowlistType::kMixed}}, |
| {"KeywordsOnly", |
| "fullscreen 'self' 'src'", |
| 1, |
| {FeaturePolicyAllowlistType::kKeywordsOnly}}, |
| {"KeywordsOnly_MultipleDirectives", |
| "fullscreen 'self' 'src'; payment 'self' 'none'", |
| 1, |
| {FeaturePolicyAllowlistType::kKeywordsOnly}}, |
| {"MultipleDirectives_SeparateTypes_Semicolon", |
| "fullscreen; geolocation 'self'", |
| 2, |
| {FeaturePolicyAllowlistType::kEmpty, FeaturePolicyAllowlistType::kSelf}}, |
| {"MultipleDirectives_SeparateTypes_Mixed", |
| "fullscreen *; geolocation 'none' " ORIGIN_A, |
| 2, |
| {FeaturePolicyAllowlistType::kStar, FeaturePolicyAllowlistType::kMixed}}, |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| FeaturePolicyAllowlistHistogramTest, |
| ::testing::ValuesIn(FeaturePolicyAllowlistHistogramTest::kCases), |
| [](const testing::TestParamInfo<AllowlistHistogramData>& param_info) { |
| return param_info.param.name; |
| }); |
| |
| TEST_P(FeaturePolicyAllowlistHistogramTest, HeaderHistogram) { |
| PolicyParserMessageBuffer logger; |
| HistogramTester tester; |
| |
| AllowlistHistogramData data = GetParam(); |
| |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| ParseFeaturePolicyHeader(data.policy_declaration, origin_a_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| for (FeaturePolicyAllowlistType expected_bucket : data.expected_buckets) { |
| tester.ExpectBucketCount(kAllowlistHeaderHistogram, |
| static_cast<int>(expected_bucket), 1); |
| } |
| |
| tester.ExpectTotalCount(kAllowlistHeaderHistogram, data.expected_total); |
| } |
| |
| TEST_F(FeaturePolicyAllowlistHistogramTest, MixedInHeaderHistogram) { |
| PolicyParserMessageBuffer logger; |
| HistogramTester tester; |
| |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| const char* declaration = "fullscreen *; geolocation 'self' " ORIGIN_A; |
| ParseFeaturePolicyHeader(declaration, origin_a_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| |
| tester.ExpectBucketCount(kAllowlistHeaderHistogram, |
| static_cast<int>(FeaturePolicyAllowlistType::kStar), |
| 1); |
| tester.ExpectBucketCount(kAllowlistHeaderHistogram, |
| static_cast<int>(FeaturePolicyAllowlistType::kMixed), |
| 1); |
| |
| tester.ExpectTotalCount(kAllowlistHeaderHistogram, 2); |
| } |
| |
| TEST_P(FeaturePolicyAllowlistHistogramTest, AttributeHistogram) { |
| PolicyParserMessageBuffer logger; |
| HistogramTester tester; |
| |
| AllowlistHistogramData data = GetParam(); |
| |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| FeaturePolicyParser::ParseAttribute(data.policy_declaration, origin_a_.get(), |
| origin_b_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| for (FeaturePolicyAllowlistType expected_bucket : data.expected_buckets) { |
| tester.ExpectBucketCount(kAllowlistAttributeHistogram, |
| static_cast<int>(expected_bucket), 1); |
| } |
| |
| tester.ExpectTotalCount(kAllowlistAttributeHistogram, data.expected_total); |
| } |
| |
| TEST_F(FeaturePolicyAllowlistHistogramTest, MixedInAttributeHistogram) { |
| PolicyParserMessageBuffer logger; |
| HistogramTester tester; |
| |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| const char* declaration = "fullscreen *; geolocation 'src' " ORIGIN_A; |
| FeaturePolicyParser::ParseAttribute(declaration, origin_a_.get(), |
| origin_b_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| |
| tester.ExpectBucketCount(kAllowlistAttributeHistogram, |
| static_cast<int>(FeaturePolicyAllowlistType::kStar), |
| 1); |
| tester.ExpectBucketCount(kAllowlistAttributeHistogram, |
| static_cast<int>(FeaturePolicyAllowlistType::kMixed), |
| 1); |
| |
| tester.ExpectTotalCount(kAllowlistAttributeHistogram, 2); |
| } |
| |
| TEST_F(FeaturePolicyAllowlistHistogramTest, SrcInAttributeHistogram) { |
| PolicyParserMessageBuffer logger; |
| HistogramTester tester; |
| |
| auto dummy = std::make_unique<DummyPageHolder>(); |
| const char* declaration = "fullscreen 'src'"; |
| FeaturePolicyParser::ParseAttribute(declaration, origin_a_.get(), |
| origin_b_.get(), logger, |
| dummy->GetFrame().DomWindow()); |
| |
| tester.ExpectBucketCount(kAllowlistAttributeHistogram, |
| static_cast<int>(FeaturePolicyAllowlistType::kSrc), |
| 1); |
| |
| tester.ExpectTotalCount(kAllowlistAttributeHistogram, 1); |
| } |
| |
| // Test policy mutation methods |
| class FeaturePolicyMutationTest : public testing::Test { |
| protected: |
| FeaturePolicyMutationTest() = default; |
| |
| ~FeaturePolicyMutationTest() override = default; |
| |
| url::Origin url_origin_a_ = url::Origin::Create(GURL(ORIGIN_A)); |
| url::Origin url_origin_b_ = url::Origin::Create(GURL(ORIGIN_B)); |
| url::Origin url_origin_c_ = url::Origin::Create(GURL(ORIGIN_C)); |
| |
| // Returns true if the policy contains a declaration for the feature which |
| // allows it in all origins. |
| bool IsFeatureAllowedEverywhere(mojom::blink::FeaturePolicyFeature feature, |
| const ParsedFeaturePolicy& policy) { |
| const auto& result = std::find_if(policy.begin(), policy.end(), |
| [feature](const auto& declaration) { |
| return declaration.feature == feature; |
| }); |
| if (result == policy.end()) |
| return false; |
| |
| return result->feature == feature && result->matches_all_origins && |
| result->matches_opaque_src && result->allowed_origins.empty(); |
| } |
| |
| // Returns true if the policy contains a declaration for the feature which |
| // disallows it in all origins. |
| bool IsFeatureDisallowedEverywhere(mojom::blink::FeaturePolicyFeature feature, |
| const ParsedFeaturePolicy& policy) { |
| const auto& result = std::find_if(policy.begin(), policy.end(), |
| [feature](const auto& declaration) { |
| return declaration.feature == feature; |
| }); |
| if (result == policy.end()) |
| return false; |
| |
| return result->feature == feature && !result->matches_all_origins && |
| !result->matches_opaque_src && result->allowed_origins.empty(); |
| } |
| |
| ParsedFeaturePolicy test_policy = { |
| {mojom::blink::FeaturePolicyFeature::kFullscreen, |
| /* allowed_origins */ {url_origin_a_, url_origin_b_}, false, false}, |
| {mojom::blink::FeaturePolicyFeature::kGeolocation, |
| /* allowed_origins */ {url_origin_a_}, false, false}}; |
| |
| ParsedFeaturePolicy empty_policy = {}; |
| }; |
| |
| TEST_F(FeaturePolicyMutationTest, TestIsFeatureDeclared) { |
| EXPECT_TRUE(IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kFullscreen, |
| test_policy)); |
| EXPECT_TRUE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| EXPECT_FALSE( |
| IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kUsb, test_policy)); |
| EXPECT_FALSE(IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kNotFound, |
| test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestIsFeatureDeclaredWithEmptyPolicy) { |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, empty_policy)); |
| EXPECT_FALSE(IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kNotFound, |
| empty_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestRemoveAbsentFeature) { |
| ASSERT_EQ(2UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kPayment, |
| test_policy)); |
| EXPECT_FALSE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kPayment, test_policy)); |
| ASSERT_EQ(2UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kPayment, |
| test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestRemoveFromEmptyPolicy) { |
| ASSERT_EQ(0UL, empty_policy.size()); |
| EXPECT_FALSE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kPayment, test_policy)); |
| ASSERT_EQ(0UL, empty_policy.size()); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestRemoveFeatureIfPresent) { |
| ASSERT_EQ(2UL, test_policy.size()); |
| EXPECT_TRUE(IsFeatureDeclared(mojom::blink::FeaturePolicyFeature::kFullscreen, |
| test_policy)); |
| EXPECT_TRUE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| EXPECT_EQ(1UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| |
| // Attempt to remove the feature again |
| EXPECT_FALSE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| EXPECT_EQ(1UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestRemoveFeatureIfPresentOnSecondFeature) { |
| ASSERT_EQ(2UL, test_policy.size()); |
| EXPECT_TRUE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| EXPECT_TRUE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| ASSERT_EQ(1UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| |
| // Attempt to remove the feature again |
| EXPECT_FALSE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| EXPECT_EQ(1UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestRemoveAllFeatures) { |
| ASSERT_EQ(2UL, test_policy.size()); |
| EXPECT_TRUE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| EXPECT_TRUE(RemoveFeatureIfPresent( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| EXPECT_EQ(0UL, test_policy.size()); |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| EXPECT_FALSE(IsFeatureDeclared( |
| mojom::blink::FeaturePolicyFeature::kGeolocation, test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestDisallowIfNotPresent) { |
| ParsedFeaturePolicy copy = test_policy; |
| // Try to disallow a feature which already exists |
| EXPECT_FALSE(DisallowFeatureIfNotPresent( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, copy)); |
| ASSERT_EQ(copy, test_policy); |
| |
| // Disallow a new feature |
| EXPECT_TRUE(DisallowFeatureIfNotPresent( |
| mojom::blink::FeaturePolicyFeature::kPayment, copy)); |
| EXPECT_EQ(3UL, copy.size()); |
| // Verify that the feature is, in fact, now disallowed everywhere |
| EXPECT_TRUE(IsFeatureDisallowedEverywhere( |
| mojom::blink::FeaturePolicyFeature::kPayment, copy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestAllowEverywhereIfNotPresent) { |
| ParsedFeaturePolicy copy = test_policy; |
| // Try to allow a feature which already exists |
| EXPECT_FALSE(AllowFeatureEverywhereIfNotPresent( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, copy)); |
| ASSERT_EQ(copy, test_policy); |
| |
| // Allow a new feature |
| EXPECT_TRUE(AllowFeatureEverywhereIfNotPresent( |
| mojom::blink::FeaturePolicyFeature::kPayment, copy)); |
| EXPECT_EQ(3UL, copy.size()); |
| // Verify that the feature is, in fact, allowed everywhere |
| EXPECT_TRUE(IsFeatureAllowedEverywhere( |
| mojom::blink::FeaturePolicyFeature::kPayment, copy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestDisallowUnconditionally) { |
| // Try to disallow a feature which already exists |
| DisallowFeature(mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy); |
| // Should not have changed the number of declarations |
| EXPECT_EQ(2UL, test_policy.size()); |
| // Verify that the feature is, in fact, now disallowed everywhere |
| EXPECT_TRUE(IsFeatureDisallowedEverywhere( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestDisallowNewFeatureUnconditionally) { |
| // Try to disallow a feature which does not yet exist |
| DisallowFeature(mojom::blink::FeaturePolicyFeature::kPayment, test_policy); |
| // Should have added a new declaration |
| EXPECT_EQ(3UL, test_policy.size()); |
| // Verify that the feature is, in fact, now disallowed everywhere |
| EXPECT_TRUE(IsFeatureDisallowedEverywhere( |
| mojom::blink::FeaturePolicyFeature::kPayment, test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestAllowUnconditionally) { |
| // Try to allow a feature which already exists |
| AllowFeatureEverywhere(mojom::blink::FeaturePolicyFeature::kFullscreen, |
| test_policy); |
| // Should not have changed the number of declarations |
| EXPECT_EQ(2UL, test_policy.size()); |
| // Verify that the feature is, in fact, now allowed everywhere |
| EXPECT_TRUE(IsFeatureAllowedEverywhere( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, test_policy)); |
| } |
| |
| TEST_F(FeaturePolicyMutationTest, TestAllowNewFeatureUnconditionally) { |
| // Try to allow a feature which does not yet exist |
| AllowFeatureEverywhere(mojom::blink::FeaturePolicyFeature::kPayment, |
| test_policy); |
| // Should have added a new declaration |
| EXPECT_EQ(3UL, test_policy.size()); |
| // Verify that the feature is, in fact, now allowed everywhere |
| EXPECT_TRUE(IsFeatureAllowedEverywhere( |
| mojom::blink::FeaturePolicyFeature::kPayment, test_policy)); |
| } |
| |
| class FeaturePolicyViolationHistogramTest : public testing::Test { |
| protected: |
| FeaturePolicyViolationHistogramTest() = default; |
| |
| ~FeaturePolicyViolationHistogramTest() override = default; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(FeaturePolicyViolationHistogramTest); |
| }; |
| |
| TEST_F(FeaturePolicyViolationHistogramTest, PotentialViolation) { |
| HistogramTester tester; |
| const char* histogram_name = |
| "Blink.UseCounter.FeaturePolicy.PotentialViolation"; |
| auto dummy_page_holder_ = std::make_unique<DummyPageHolder>(); |
| // Probing feature state should not count. |
| dummy_page_holder_->GetFrame().DomWindow()->IsFeatureEnabled( |
| mojom::blink::FeaturePolicyFeature::kPayment); |
| tester.ExpectTotalCount(histogram_name, 0); |
| // Checking the feature state with reporting intent should record a potential |
| // violation. |
| dummy_page_holder_->GetFrame().DomWindow()->IsFeatureEnabled( |
| mojom::blink::FeaturePolicyFeature::kPayment, |
| ReportOptions::kReportOnFailure); |
| tester.ExpectTotalCount(histogram_name, 1); |
| // The potential violation for an already recorded violation does not count |
| // again. |
| dummy_page_holder_->GetFrame().DomWindow()->IsFeatureEnabled( |
| mojom::blink::FeaturePolicyFeature::kPayment, |
| ReportOptions::kReportOnFailure); |
| tester.ExpectTotalCount(histogram_name, 1); |
| // Sanity check: check some other feature to increase the count. |
| dummy_page_holder_->GetFrame().DomWindow()->IsFeatureEnabled( |
| mojom::blink::FeaturePolicyFeature::kFullscreen, |
| ReportOptions::kReportOnFailure); |
| tester.ExpectTotalCount(histogram_name, 2); |
| } |
| |
| } // namespace blink |