Filter out Origin-trial features from Feature Policy JS API

Bug: 939953
Change-Id: I856d141f02b483c5fcb8a506e2ed66308395ea99
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1511828
Commit-Queue: Ian Clelland <iclelland@chromium.org>
Reviewed-by: Luna Lu <loonybear@chromium.org>
Cr-Commit-Position: refs/heads/master@{#639602}
diff --git a/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc b/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc
index b8da5b0..6aefc32 100644
--- a/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/dom_feature_policy.cc
@@ -7,13 +7,17 @@
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/feature_policy/feature_policy.h"
 #include "third_party/blink/renderer/core/inspector/console_message.h"
+#include "third_party/blink/renderer/platform/bindings/script_state.h"
 #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
 #include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
 
 namespace blink {
 
-bool DOMFeaturePolicy::allowsFeature(const String& feature) const {
-  if (GetDefaultFeatureNameMap().Contains(feature)) {
+bool DOMFeaturePolicy::allowsFeature(ScriptState* script_state,
+                                     const String& feature) const {
+  ExecutionContext* execution_context =
+      script_state ? ExecutionContext::From(script_state) : nullptr;
+  if (GetAvailableFeatures(execution_context).Contains(feature)) {
     auto feature_name = GetDefaultFeatureNameMap().at(feature);
     mojom::PolicyValueType feature_type =
         GetPolicy()->GetFeatureList().at(feature_name).second;
@@ -25,8 +29,11 @@
   return false;
 }
 
-bool DOMFeaturePolicy::allowsFeature(const String& feature,
+bool DOMFeaturePolicy::allowsFeature(ScriptState* script_state,
+                                     const String& feature,
                                      const String& url) const {
+  ExecutionContext* execution_context =
+      script_state ? ExecutionContext::From(script_state) : nullptr;
   scoped_refptr<const SecurityOrigin> origin =
       SecurityOrigin::CreateFromString(url);
   if (!origin || origin->IsOpaque()) {
@@ -36,7 +43,7 @@
     return false;
   }
 
-  if (!GetDefaultFeatureNameMap().Contains(feature)) {
+  if (!GetAvailableFeatures(execution_context).Contains(feature)) {
     AddWarningForUnrecognizedFeature(feature);
     return false;
   }
@@ -49,29 +56,34 @@
                                                 origin->ToUrlOrigin(), value);
 }
 
-Vector<String> DOMFeaturePolicy::features() const {
-  Vector<String> features;
-  for (const auto& entry : GetDefaultFeatureNameMap())
-    features.push_back(entry.key);
-  return features;
+Vector<String> DOMFeaturePolicy::features(ScriptState* script_state) const {
+  ExecutionContext* execution_context =
+      script_state ? ExecutionContext::From(script_state) : nullptr;
+  return GetAvailableFeatures(execution_context);
 }
 
-Vector<String> DOMFeaturePolicy::allowedFeatures() const {
+Vector<String> DOMFeaturePolicy::allowedFeatures(
+    ScriptState* script_state) const {
+  ExecutionContext* execution_context =
+      script_state ? ExecutionContext::From(script_state) : nullptr;
   Vector<String> allowed_features;
-  for (const auto& entry : GetDefaultFeatureNameMap()) {
-    auto feature_name = entry.value;
+  for (const String& feature : GetAvailableFeatures(execution_context)) {
+    auto feature_name = GetDefaultFeatureNameMap().at(feature);
     mojom::PolicyValueType feature_type =
         GetPolicy()->GetFeatureList().at(feature_name).second;
     PolicyValue value = PolicyValue::CreateMaxPolicyValue(feature_type);
     if (GetPolicy()->IsFeatureEnabled(feature_name, value))
-      allowed_features.push_back(entry.key);
+      allowed_features.push_back(feature);
   }
   return allowed_features;
 }
 
 Vector<String> DOMFeaturePolicy::getAllowlistForFeature(
+    ScriptState* script_state,
     const String& feature) const {
-  if (GetDefaultFeatureNameMap().Contains(feature)) {
+  ExecutionContext* execution_context =
+      script_state ? ExecutionContext::From(script_state) : nullptr;
+  if (GetAvailableFeatures(execution_context).Contains(feature)) {
     auto feature_name = GetDefaultFeatureNameMap().at(feature);
     auto feature_type = GetPolicy()->GetFeatureList().at(feature_name).second;
 
diff --git a/third_party/blink/renderer/core/feature_policy/dom_feature_policy.h b/third_party/blink/renderer/core/feature_policy/dom_feature_policy.h
index e9766df..adac437 100644
--- a/third_party/blink/renderer/core/feature_policy/dom_feature_policy.h
+++ b/third_party/blink/renderer/core/feature_policy/dom_feature_policy.h
@@ -13,6 +13,7 @@
 namespace blink {
 
 class Document;
+class ScriptState;
 class SecurityOrigin;
 
 // DOMFeaturePolicy provides an interface for feature policy introspection of a
@@ -26,17 +27,20 @@
   // Implementation of methods of the policy interface:
   // Returns whether or not the given feature is allowed on the origin of the
   // document that owns the policy.
-  bool allowsFeature(const String& feature) const;
+  bool allowsFeature(ScriptState* script_state, const String& feature) const;
   // Returns whether or not the given feature is allowed on the origin of the
   // given URL.
-  bool allowsFeature(const String& feature, const String& url) const;
+  bool allowsFeature(ScriptState* script_state,
+                     const String& feature,
+                     const String& url) const;
   // Returns a list of feature names that are supported by the user agent.
-  Vector<String> features() const;
+  Vector<String> features(ScriptState* script_state) const;
   // Returns a list of feature names that are allowed on the self origin.
-  Vector<String> allowedFeatures() const;
+  Vector<String> allowedFeatures(ScriptState* script_state) const;
   // Returns a list of feature name that are allowed on the origin of the given
   // URL.
-  Vector<String> getAllowlistForFeature(const String& url) const;
+  Vector<String> getAllowlistForFeature(ScriptState* script_state,
+                                        const String& url) const;
 
   // Inform the DOMFeaturePolicy object when the container policy on its frame
   // element has changed.
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.cc b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
index e7ea055..cd38b8d 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.cc
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.cc
@@ -305,6 +305,8 @@
                                  mojom::FeaturePolicyFeature::kSpeaker);
     default_feature_name_map.Set("sync-xhr",
                                  mojom::FeaturePolicyFeature::kSyncXHR);
+    default_feature_name_map.Set("frobulate",
+                                 mojom::FeaturePolicyFeature::kFrobulate);
     // Under origin trial: Should be made conditional on WebVR and WebXR
     // runtime flags once it is out of trial.
     ASSERT_ORIGIN_TRIAL(WebVR);
@@ -391,6 +393,15 @@
   return default_feature_name_map;
 }
 
+const Vector<String> GetAvailableFeatures(ExecutionContext* execution_context) {
+  Vector<String> available_features;
+  for (const auto& feature : GetDefaultFeatureNameMap()) {
+    if (!IsOriginTrialDisabled(feature.key, execution_context))
+      available_features.push_back(feature.key);
+  }
+  return available_features;
+}
+
 const String& GetNameForFeature(mojom::FeaturePolicyFeature feature) {
   for (const auto& entry : GetDefaultFeatureNameMap()) {
     if (entry.value == feature)
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.h b/third_party/blink/renderer/core/feature_policy/feature_policy.h
index 9cf3ee59..54e5521 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.h
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.h
@@ -26,6 +26,10 @@
 typedef HashMap<String, mojom::FeaturePolicyFeature> FeatureNameMap;
 CORE_EXPORT const FeatureNameMap& GetDefaultFeatureNameMap();
 
+// Returns the list of features which are currently available in this context,
+// including any features which have been made available by an origin trial.
+CORE_EXPORT const Vector<String> GetAvailableFeatures(ExecutionContext*);
+
 // Converts a header policy string into a vector of allowlists, one for each
 // feature specified. Unrecognized features are filtered out. If |messages| is
 // not null, then any message in the input will cause a warning message to be
diff --git a/third_party/blink/renderer/core/feature_policy/feature_policy.idl b/third_party/blink/renderer/core/feature_policy/feature_policy.idl
index e2f45f8..cebf39d 100644
--- a/third_party/blink/renderer/core/feature_policy/feature_policy.idl
+++ b/third_party/blink/renderer/core/feature_policy/feature_policy.idl
@@ -8,8 +8,8 @@
   RuntimeEnabled=FeaturePolicyJavaScriptInterface,
   ImplementedAs=DOMFeaturePolicy
 ] interface FeaturePolicy {
-  [MeasureAs=FeaturePolicyJSAPI] boolean allowsFeature(DOMString feature, optional DOMString url);
-  [MeasureAs=FeaturePolicyJSAPI] sequence<DOMString> features();
-  [MeasureAs=FeaturePolicyJSAPI] sequence<DOMString> allowedFeatures();
-  [MeasureAs=FeaturePolicyJSAPI] sequence<DOMString> getAllowlistForFeature(DOMString feature);
+  [MeasureAs=FeaturePolicyJSAPI, CallWith=ScriptState] boolean allowsFeature(DOMString feature, optional DOMString url);
+  [MeasureAs=FeaturePolicyJSAPI, CallWith=ScriptState] sequence<DOMString> features();
+  [MeasureAs=FeaturePolicyJSAPI, CallWith=ScriptState] sequence<DOMString> allowedFeatures();
+  [MeasureAs=FeaturePolicyJSAPI, CallWith=ScriptState] sequence<DOMString> getAllowlistForFeature(DOMString feature);
 };
diff --git a/third_party/blink/renderer/core/feature_policy/policy_test.cc b/third_party/blink/renderer/core/feature_policy/policy_test.cc
index 3101b3d..b7a13b6e 100644
--- a/third_party/blink/renderer/core/feature_policy/policy_test.cc
+++ b/third_party/blink/renderer/core/feature_policy/policy_test.cc
@@ -57,40 +57,42 @@
 };
 
 TEST_F(DocumentPolicyTest, TestAllowsFeature) {
-  EXPECT_FALSE(GetPolicy()->allowsFeature("badfeature"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("midi"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("midi", kSelfOrigin));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("fullscreen"));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("fullscreen", kOriginA));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("payment"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("payment", kOriginA));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("payment", kOriginB));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("camera"));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("camera", kOriginA));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("camera", kOriginB));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("camera", "https://badorigin.com"));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("geolocation", kSelfOrigin));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("sync-xhr"));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("sync-xhr", kOriginA));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "badfeature"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "midi"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "midi", kSelfOrigin));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "fullscreen"));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "fullscreen", kOriginA));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "payment"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "payment", kOriginA));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "payment", kOriginB));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "camera"));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "camera", kOriginA));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "camera", kOriginB));
+  EXPECT_FALSE(
+      GetPolicy()->allowsFeature(nullptr, "camera", "https://badorigin.com"));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "geolocation", kSelfOrigin));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "sync-xhr"));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "sync-xhr", kOriginA));
 }
 
 TEST_F(DocumentPolicyTest, TestGetAllowList) {
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("camera"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "camera"),
               UnorderedElementsAre(kSelfOrigin, kOriginA, kOriginB));
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("payment"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "payment"),
               UnorderedElementsAre(kSelfOrigin));
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("geolocation"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "geolocation"),
               UnorderedElementsAre(kSelfOrigin));
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("fullscreen"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "fullscreen"),
               UnorderedElementsAre("*"));
-  EXPECT_TRUE(GetPolicy()->getAllowlistForFeature("badfeature").IsEmpty());
-  EXPECT_TRUE(GetPolicy()->getAllowlistForFeature("midi").IsEmpty());
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("sync-xhr"),
+  EXPECT_TRUE(
+      GetPolicy()->getAllowlistForFeature(nullptr, "badfeature").IsEmpty());
+  EXPECT_TRUE(GetPolicy()->getAllowlistForFeature(nullptr, "midi").IsEmpty());
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "sync-xhr"),
               UnorderedElementsAre("*"));
 }
 
 TEST_F(DocumentPolicyTest, TestAllowedFeatures) {
-  Vector<String> allowed_features = GetPolicy()->allowedFeatures();
+  Vector<String> allowed_features = GetPolicy()->allowedFeatures(nullptr);
   EXPECT_TRUE(allowed_features.Contains("fullscreen"));
   EXPECT_TRUE(allowed_features.Contains("payment"));
   EXPECT_TRUE(allowed_features.Contains("camera"));
@@ -103,41 +105,43 @@
 }
 
 TEST_F(IFramePolicyTest, TestAllowsFeature) {
-  EXPECT_FALSE(GetPolicy()->allowsFeature("badfeature"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("midi"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("midi", kSelfOrigin));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("fullscreen"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("fullscreen", kOriginA));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("fullscreen", kSelfOrigin));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("payment"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("payment", kOriginA));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("payment", kOriginB));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("camera"));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("camera", kOriginA));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("camera", kOriginB));
-  EXPECT_FALSE(GetPolicy()->allowsFeature("camera", "https://badorigin.com"));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("geolocation", kSelfOrigin));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("sync-xhr"));
-  EXPECT_TRUE(GetPolicy()->allowsFeature("sync-xhr", kOriginA));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "badfeature"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "midi"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "midi", kSelfOrigin));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "fullscreen"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "fullscreen", kOriginA));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "fullscreen", kSelfOrigin));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "payment"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "payment", kOriginA));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "payment", kOriginB));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "camera"));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "camera", kOriginA));
+  EXPECT_FALSE(GetPolicy()->allowsFeature(nullptr, "camera", kOriginB));
+  EXPECT_FALSE(
+      GetPolicy()->allowsFeature(nullptr, "camera", "https://badorigin.com"));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "geolocation", kSelfOrigin));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "sync-xhr"));
+  EXPECT_TRUE(GetPolicy()->allowsFeature(nullptr, "sync-xhr", kOriginA));
 }
 
 TEST_F(IFramePolicyTest, TestGetAllowList) {
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("camera"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "camera"),
               UnorderedElementsAre(kSelfOrigin));
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("payment"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "payment"),
               UnorderedElementsAre(kSelfOrigin));
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("geolocation"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "geolocation"),
               UnorderedElementsAre(kSelfOrigin));
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("fullscreen"),
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "fullscreen"),
               UnorderedElementsAre(kSelfOrigin));
-  EXPECT_TRUE(GetPolicy()->getAllowlistForFeature("badfeature").IsEmpty());
-  EXPECT_TRUE(GetPolicy()->getAllowlistForFeature("midi").IsEmpty());
-  EXPECT_THAT(GetPolicy()->getAllowlistForFeature("sync-xhr"),
+  EXPECT_TRUE(
+      GetPolicy()->getAllowlistForFeature(nullptr, "badfeature").IsEmpty());
+  EXPECT_TRUE(GetPolicy()->getAllowlistForFeature(nullptr, "midi").IsEmpty());
+  EXPECT_THAT(GetPolicy()->getAllowlistForFeature(nullptr, "sync-xhr"),
               UnorderedElementsAre("*"));
 }
 
 TEST_F(IFramePolicyTest, TestAllowedFeatures) {
-  Vector<String> allowed_features = GetPolicy()->allowedFeatures();
+  Vector<String> allowed_features = GetPolicy()->allowedFeatures(nullptr);
   EXPECT_TRUE(allowed_features.Contains("fullscreen"));
   EXPECT_TRUE(allowed_features.Contains("payment"));
   EXPECT_TRUE(allowed_features.Contains("camera"));
@@ -156,7 +160,7 @@
       SecurityOrigin::CreateFromString(kOriginA), nullptr);
   GetPolicy()->UpdateContainerPolicy(
       container_policy, SecurityOrigin::CreateFromString(kOriginA));
-  Vector<String> allowed_features = GetPolicy()->allowedFeatures();
+  Vector<String> allowed_features = GetPolicy()->allowedFeatures(nullptr);
   EXPECT_TRUE(allowed_features.Contains("fullscreen"));
   EXPECT_FALSE(allowed_features.Contains("payment"));
   EXPECT_TRUE(allowed_features.Contains("geolocation"));
diff --git a/third_party/blink/web_tests/http/tests/feature-policy/no_origin_trial_policy.php b/third_party/blink/web_tests/http/tests/feature-policy/no_origin_trial_policy.php
new file mode 100644
index 0000000..56568fa
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/feature-policy/no_origin_trial_policy.php
@@ -0,0 +1,33 @@
+<?php
+// Copyright 2019 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.
+
+header("Feature-Policy: frobulate *");
+?>
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  assert_false(document.featurePolicy.allowsFeature("frobulate"));
+}, 'Test featurePolicy.allowsFeature() on unavailable origin trial feature');
+
+test(function() {
+  assert_false(document.featurePolicy.allowsFeature("frobulate", "https://www.example.com"));
+}, 'Test featurePolicy.allowsFeature() on unavailable origin trial feature for specific origin');
+
+test(function() {
+  assert_false(document.featurePolicy.features().includes("frobulate"));
+}, 'Test featurePolicy.features() should not include origin trial feature');
+
+test(function() {
+  assert_false(document.featurePolicy.allowedFeatures().includes("frobulate"));
+}, 'Test featurePolicy.allowedFeatures() should not include origin trial feature');
+
+test(function() {
+  assert_array_equals(
+    document.featurePolicy.getAllowlistForFeature("frobulate"), [])
+}, "Origin trial features should not have an allowlist.");
+
+</script>
diff --git a/third_party/blink/web_tests/http/tests/feature-policy/origin_trial_policy.php b/third_party/blink/web_tests/http/tests/feature-policy/origin_trial_policy.php
new file mode 100644
index 0000000..52903ed
--- /dev/null
+++ b/third_party/blink/web_tests/http/tests/feature-policy/origin_trial_policy.php
@@ -0,0 +1,40 @@
+<?php
+// Copyright 2019 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.
+
+header("Feature-Policy: frobulate *");
+
+// TODO(iclelland): Generate this sample token during the build. The token
+// below will expire in 2033, but it would be better to always have a token which
+// is guaranteed to be valid when the tests are run.
+// Generate this token with the command:
+// generate_token.py http://127.0.0.1:8000 Frobulate -expire-timestamp=2000000000
+header("Origin-Trial: AlCoOPbezqtrGMzSzbLQC4c+oPqO6yuioemcBPjgcXajF8jtmZr4B8tJRPAARPbsX6hDeVyXCKHzEJfpBXvZgQEAAABReyJvcmlnaW4iOiAiaHR0cDovLzEyNy4wLjAuMTo4MDAwIiwgImZlYXR1cmUiOiAiRnJvYnVsYXRlIiwgImV4cGlyeSI6IDIwMDAwMDAwMDB9");
+?>
+<!DOCTYPE html>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  assert_true(document.featurePolicy.allowsFeature("frobulate"));
+}, 'Test featurePolicy.allowsFeature() on origin trial feature');
+
+test(function() {
+  assert_true(document.featurePolicy.allowsFeature("frobulate", "https://www.example.com"));
+}, 'Test featurePolicy.allowsFeature() on origin trial feature for specific origin');
+
+test(function() {
+  assert_true(document.featurePolicy.features().includes("frobulate"));
+}, 'Test featurePolicy.features() includes origin trial feature');
+
+test(function() {
+  assert_true(document.featurePolicy.allowedFeatures().includes("frobulate"));
+}, 'Test featurePolicy.allowedFeatures() includes origin trial feature');
+
+test(function() {
+  assert_array_equals(
+    document.featurePolicy.getAllowlistForFeature("frobulate"), ["*"])
+}, "Origin trial feature is allowed for all in main frame");
+
+</script>