Commit a3aa96ab authored by Bryan McQuade's avatar Bryan McQuade Committed by Commit Bot

Navigation origin trial APIs.

See https://docs.google.com/document/d/1bPWScr35Wv-xPy85JQ3mf12AXJ7AqSFqen6MxxslSJM/edit for details.

Change-Id: I10376ff5a34d1025f09372ecc1b34365faec312c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1589699Reviewed-by: default avatarJason Chase <chasej@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Commit-Queue: Bryan McQuade <bmcquade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#658723}
parent a3296099
# Blink/OWP Security reviewers
dcheng@chromium.org
mkwst@chromium.org
palmer@chromium.org
vogelheim@chromium.org
...@@ -6,6 +6,7 @@ import("//third_party/blink/renderer/core/core.gni") ...@@ -6,6 +6,7 @@ import("//third_party/blink/renderer/core/core.gni")
blink_core_sources("origin_trials") { blink_core_sources("origin_trials") {
sources = [ sources = [
"navigation_origin_trial_features.cc",
"origin_trial_context.cc", "origin_trial_context.cc",
"origin_trial_context.h", "origin_trial_context.h",
"origin_trials.h", "origin_trials.h",
......
file://third_party/blink/common/origin_trials/OWNERS file://third_party/blink/common/origin_trials/OWNERS
per-file navigation_origin_trial_features.cc=set noparent
per-file navigation_origin_trial_features.cc=file://third_party/blink/SECURITY_OWNERS
// 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.
// This file provides GetNavigationOriginTrialFeatures which is declared in
// origin_trials.h. GetNavigationOriginTrialFeatures is defined in this file
// since changes to it require review from security reviewers, listed in the
// SECURITY_OWNERS file.
#include "third_party/blink/renderer/core/origin_trials/origin_trials.h"
namespace blink {
namespace origin_trials {
const HashSet<OriginTrialFeature>& GetNavigationOriginTrialFeatures() {
DEFINE_THREAD_SAFE_STATIC_LOCAL(
HashSet<OriginTrialFeature>, navigation_origin_trial_features,
({// Enable the kOriginTrialsSampleAPINavigation feature as a navigation
// feature, for tests.
OriginTrialFeature::kOriginTrialsSampleAPINavigation}));
return navigation_origin_trial_features;
}
} // namespace origin_trials
} // namespace blink
...@@ -85,6 +85,13 @@ String ExtractTokenOrQuotedString(const String& header_value, unsigned& pos) { ...@@ -85,6 +85,13 @@ String ExtractTokenOrQuotedString(const String& header_value, unsigned& pos) {
return result; return result;
} }
// Returns whether the given feature can be activated across navigations. Only
// features reviewed and approved by security reviewers can be activated across
// navigations.
bool IsCrossNavigationFeature(OriginTrialFeature feature) {
return origin_trials::GetNavigationOriginTrialFeatures().Contains(feature);
}
} // namespace } // namespace
OriginTrialContext::OriginTrialContext( OriginTrialContext::OriginTrialContext(
...@@ -153,6 +160,15 @@ void OriginTrialContext::AddTokens(ExecutionContext* context, ...@@ -153,6 +160,15 @@ void OriginTrialContext::AddTokens(ExecutionContext* context,
FromOrCreate(context)->AddTokens(*tokens); FromOrCreate(context)->AddTokens(*tokens);
} }
// static
void OriginTrialContext::ActivateNavigationFeaturesFromInitiator(
ExecutionContext* context,
const Vector<OriginTrialFeature>* features) {
if (!features || features->IsEmpty())
return;
FromOrCreate(context)->ActivateNavigationFeaturesFromInitiator(*features);
}
// static // static
std::unique_ptr<Vector<String>> OriginTrialContext::GetTokens( std::unique_ptr<Vector<String>> OriginTrialContext::GetTokens(
ExecutionContext* execution_context) { ExecutionContext* execution_context) {
...@@ -162,6 +178,28 @@ std::unique_ptr<Vector<String>> OriginTrialContext::GetTokens( ...@@ -162,6 +178,28 @@ std::unique_ptr<Vector<String>> OriginTrialContext::GetTokens(
return std::make_unique<Vector<String>>(context->tokens_); return std::make_unique<Vector<String>>(context->tokens_);
} }
// static
std::unique_ptr<Vector<OriginTrialFeature>>
OriginTrialContext::GetEnabledNavigationFeatures(
ExecutionContext* execution_context) {
const OriginTrialContext* context = From(execution_context);
return context ? context->GetEnabledNavigationFeatures() : nullptr;
}
std::unique_ptr<Vector<OriginTrialFeature>>
OriginTrialContext::GetEnabledNavigationFeatures() const {
if (enabled_features_.IsEmpty())
return nullptr;
std::unique_ptr<Vector<OriginTrialFeature>> result =
std::make_unique<Vector<OriginTrialFeature>>();
for (const OriginTrialFeature& feature : enabled_features_) {
if (IsCrossNavigationFeature(feature)) {
result->push_back(feature);
}
}
return result->IsEmpty() ? nullptr : std::move(result);
}
void OriginTrialContext::AddToken(const String& token) { void OriginTrialContext::AddToken(const String& token) {
if (token.IsEmpty()) if (token.IsEmpty())
return; return;
...@@ -191,6 +229,16 @@ void OriginTrialContext::AddTokens(const Vector<String>& tokens) { ...@@ -191,6 +229,16 @@ void OriginTrialContext::AddTokens(const Vector<String>& tokens) {
} }
} }
void OriginTrialContext::ActivateNavigationFeaturesFromInitiator(
const Vector<OriginTrialFeature>& features) {
for (const OriginTrialFeature& feature : features) {
if (IsCrossNavigationFeature(feature)) {
navigation_activated_features_.insert(feature);
}
}
InitializePendingFeatures();
}
void OriginTrialContext::InitializePendingFeatures() { void OriginTrialContext::InitializePendingFeatures() {
if (!enabled_features_.size()) if (!enabled_features_.size())
return; return;
...@@ -207,13 +255,21 @@ void OriginTrialContext::InitializePendingFeatures() { ...@@ -207,13 +255,21 @@ void OriginTrialContext::InitializePendingFeatures() {
return; return;
ScriptState::Scope scope(script_state); ScriptState::Scope scope(script_state);
for (OriginTrialFeature enabled_feature : enabled_features_) { for (OriginTrialFeature enabled_feature : enabled_features_) {
if (installed_features_.Contains(enabled_feature)) InstallFeature(enabled_feature, script_state);
continue; }
InstallPendingOriginTrialFeature(enabled_feature, script_state); for (OriginTrialFeature enabled_feature : navigation_activated_features_) {
installed_features_.insert(enabled_feature); InstallFeature(enabled_feature, script_state);
} }
} }
void OriginTrialContext::InstallFeature(OriginTrialFeature enabled_feature,
ScriptState* script_state) {
if (installed_features_.Contains(enabled_feature))
return;
InstallPendingOriginTrialFeature(enabled_feature, script_state);
installed_features_.insert(enabled_feature);
}
void OriginTrialContext::AddFeature(OriginTrialFeature feature) { void OriginTrialContext::AddFeature(OriginTrialFeature feature) {
enabled_features_.insert(feature); enabled_features_.insert(feature);
InitializePendingFeatures(); InitializePendingFeatures();
...@@ -243,6 +299,14 @@ bool OriginTrialContext::IsFeatureEnabled(OriginTrialFeature feature) const { ...@@ -243,6 +299,14 @@ bool OriginTrialContext::IsFeatureEnabled(OriginTrialFeature feature) const {
return context->IsFeatureEnabled(feature); return context->IsFeatureEnabled(feature);
} }
bool OriginTrialContext::IsNavigationFeatureActivated(
OriginTrialFeature feature) const {
if (!RuntimeEnabledFeatures::OriginTrialsEnabled())
return false;
return navigation_activated_features_.Contains(feature);
}
bool OriginTrialContext::EnableTrialFromToken(const String& token) { bool OriginTrialContext::EnableTrialFromToken(const String& token) {
DCHECK(!token.IsEmpty()); DCHECK(!token.IsEmpty());
......
...@@ -66,9 +66,28 @@ class CORE_EXPORT OriginTrialContext final ...@@ -66,9 +66,28 @@ class CORE_EXPORT OriginTrialContext final
// Returns null if no tokens were added to the ExecutionContext. // Returns null if no tokens were added to the ExecutionContext.
static std::unique_ptr<Vector<String>> GetTokens(ExecutionContext*); static std::unique_ptr<Vector<String>> GetTokens(ExecutionContext*);
// Returns the navigation trial features that are enabled in the specified
// ExecutionContext, that should be forwarded to (and activated in)
// ExecutionContexts navigated to from the given ExecutionContext. Returns
// null if no such trials were added to the ExecutionContext.
static std::unique_ptr<Vector<OriginTrialFeature>>
GetEnabledNavigationFeatures(ExecutionContext*);
// Activates navigation trial features forwarded from the ExecutionContext
// that navigated to the specified ExecutionContext. Only features for which
// origin_trials::IsCrossNavigationFeature returns true can be activated via
// this method. Trials activated via this method will return true from
// IsNavigationFeatureActivated, for the specified ExecutionContext.
static void ActivateNavigationFeaturesFromInitiator(
ExecutionContext*,
const Vector<OriginTrialFeature>*);
void AddToken(const String& token); void AddToken(const String& token);
void AddTokens(const Vector<String>& tokens); void AddTokens(const Vector<String>& tokens);
void ActivateNavigationFeaturesFromInitiator(
const Vector<OriginTrialFeature>& features);
// Forces a given origin-trial-enabled feature to be enabled in this context // Forces a given origin-trial-enabled feature to be enabled in this context
// and immediately adds required bindings to already initialized JS contexts. // and immediately adds required bindings to already initialized JS contexts.
void AddFeature(OriginTrialFeature feature); void AddFeature(OriginTrialFeature feature);
...@@ -77,6 +96,16 @@ class CORE_EXPORT OriginTrialContext final ...@@ -77,6 +96,16 @@ class CORE_EXPORT OriginTrialContext final
// execution context. // execution context.
bool IsFeatureEnabled(OriginTrialFeature feature) const; bool IsFeatureEnabled(OriginTrialFeature feature) const;
std::unique_ptr<Vector<OriginTrialFeature>> GetEnabledNavigationFeatures()
const;
// Returns true if the navigation feature is activated in the current
// ExecutionContext. Navigation features are features that are enabled in one
// ExecutionContext, but whose behavior is activated in ExecutionContexts that
// are navigated to from that context. For example, if navigating from context
// A to B, a navigation feature is enabled in A, and activated in B.
bool IsNavigationFeatureActivated(const OriginTrialFeature feature) const;
// Installs JavaScript bindings on the relevant objects for any features which // Installs JavaScript bindings on the relevant objects for any features which
// should be enabled by the current set of trial tokens. This method is called // should be enabled by the current set of trial tokens. This method is called
// every time a token is added to the document (including when tokens are // every time a token is added to the document (including when tokens are
...@@ -96,9 +125,14 @@ class CORE_EXPORT OriginTrialContext final ...@@ -96,9 +125,14 @@ class CORE_EXPORT OriginTrialContext final
// the token is valid. // the token is valid.
bool EnableTrialFromToken(const String& token); bool EnableTrialFromToken(const String& token);
// Installs JavaScript bindings on the relevant objects for the specified
// OriginTrialFeature.
void InstallFeature(OriginTrialFeature, ScriptState*);
Vector<String> tokens_; Vector<String> tokens_;
HashSet<OriginTrialFeature> enabled_features_; HashSet<OriginTrialFeature> enabled_features_;
HashSet<OriginTrialFeature> installed_features_; HashSet<OriginTrialFeature> installed_features_;
HashSet<OriginTrialFeature> navigation_activated_features_;
std::unique_ptr<TrialTokenValidator> trial_token_validator_; std::unique_ptr<TrialTokenValidator> trial_token_validator_;
}; };
......
...@@ -27,6 +27,7 @@ namespace blink { ...@@ -27,6 +27,7 @@ namespace blink {
namespace { namespace {
const char kFrobulateTrialName[] = "Frobulate"; const char kFrobulateTrialName[] = "Frobulate";
const char kFrobulateNavigationTrialName[] = "FrobulateNavigation";
const char kFrobulateEnabledOrigin[] = "https://www.example.com"; const char kFrobulateEnabledOrigin[] = "https://www.example.com";
const char kFrobulateEnabledOriginUnsecure[] = "http://www.example.com"; const char kFrobulateEnabledOriginUnsecure[] = "http://www.example.com";
...@@ -99,6 +100,15 @@ class OriginTrialContextTest : public testing::Test, ...@@ -99,6 +100,15 @@ class OriginTrialContextTest : public testing::Test,
return origin_trial_context_->IsFeatureEnabled(feature); return origin_trial_context_->IsFeatureEnabled(feature);
} }
std::unique_ptr<Vector<OriginTrialFeature>> GetEnabledNavigationFeatures() {
return origin_trial_context_->GetEnabledNavigationFeatures();
}
bool ActivateNavigationFeature(OriginTrialFeature feature) {
origin_trial_context_->ActivateNavigationFeaturesFromInitiator({feature});
return origin_trial_context_->IsNavigationFeatureActivated(feature);
}
void ExpectStatusUniqueMetric(OriginTrialTokenStatus status, int count) { void ExpectStatusUniqueMetric(OriginTrialTokenStatus status, int count) {
histogram_tester_->ExpectUniqueSample(kResultHistogram, histogram_tester_->ExpectUniqueSample(kResultHistogram,
static_cast<int>(status), count); static_cast<int>(status), count);
...@@ -137,6 +147,10 @@ TEST_F(OriginTrialContextTest, EnabledSecureRegisteredOrigin) { ...@@ -137,6 +147,10 @@ TEST_F(OriginTrialContextTest, EnabledSecureRegisteredOrigin) {
// Status metric should be updated. // Status metric should be updated.
ExpectStatusUniqueMetric(OriginTrialTokenStatus::kSuccess, 1); ExpectStatusUniqueMetric(OriginTrialTokenStatus::kSuccess, 1);
// kOriginTrialsSampleAPI is not a navigation feature, so shouldn't be
// included in GetEnabledNavigationFeatures().
EXPECT_EQ(nullptr, GetEnabledNavigationFeatures());
} }
// ... but if the browser says it's invalid for any reason, that's enough to // ... but if the browser says it's invalid for any reason, that's enough to
...@@ -252,4 +266,25 @@ TEST_F(OriginTrialContextTest, FeaturePolicy) { ...@@ -252,4 +266,25 @@ TEST_F(OriginTrialContextTest, FeaturePolicy) {
EXPECT_EQ(mojom::FeaturePolicyFeature::kFrobulate, result[0].feature); EXPECT_EQ(mojom::FeaturePolicyFeature::kFrobulate, result[0].feature);
} }
TEST_F(OriginTrialContextTest, GetEnabledNavigationFeatures) {
TokenValidator()->SetResponse(OriginTrialTokenStatus::kSuccess,
kFrobulateNavigationTrialName);
EXPECT_TRUE(
IsFeatureEnabled(kFrobulateEnabledOrigin,
OriginTrialFeature::kOriginTrialsSampleAPINavigation));
auto enabled_navigation_features = GetEnabledNavigationFeatures();
ASSERT_NE(nullptr, enabled_navigation_features.get());
EXPECT_EQ(WTF::Vector<OriginTrialFeature>(
{OriginTrialFeature::kOriginTrialsSampleAPINavigation}),
*enabled_navigation_features.get());
}
TEST_F(OriginTrialContextTest, ActivateNavigationFeature) {
EXPECT_TRUE(ActivateNavigationFeature(
OriginTrialFeature::kOriginTrialsSampleAPINavigation));
EXPECT_FALSE(
ActivateNavigationFeature(OriginTrialFeature::kOriginTrialsSampleAPI));
}
} // namespace blink } // namespace blink
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "third_party/blink/renderer/core/core_export.h" #include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h" #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h" #include "third_party/blink/renderer/platform/wtf/vector.h"
...@@ -33,6 +34,8 @@ Vector<OriginTrialFeature> GetImpliedFeatures(OriginTrialFeature feature); ...@@ -33,6 +34,8 @@ Vector<OriginTrialFeature> GetImpliedFeatures(OriginTrialFeature feature);
bool FeatureEnabledForOS(OriginTrialFeature feature); bool FeatureEnabledForOS(OriginTrialFeature feature);
const HashSet<OriginTrialFeature>& GetNavigationOriginTrialFeatures();
} // namespace origin_trials } // namespace origin_trials
} // namespace blink } // namespace blink
......
...@@ -1010,6 +1010,10 @@ ...@@ -1010,6 +1010,10 @@
origin_trial_feature_name: "FrobulateInvalidOS", origin_trial_feature_name: "FrobulateInvalidOS",
origin_trial_os: ["invalid"], origin_trial_os: ["invalid"],
}, },
{
name: "OriginTrialsSampleAPINavigation",
origin_trial_feature_name: "FrobulateNavigation",
},
{ {
name: "OutOfBlinkCors", name: "OutOfBlinkCors",
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment