Index: third_party/WebKit/Source/core/origin_trials/OriginTrialContext.cpp |
diff --git a/third_party/WebKit/Source/core/origin_trials/OriginTrialContext.cpp b/third_party/WebKit/Source/core/origin_trials/OriginTrialContext.cpp |
index 1bae6e6b34edaaddee0088a05dd94140ad3f86bc..10dfde89ae25bfe9d2d36dee949ed6a9386c50b1 100644 |
--- a/third_party/WebKit/Source/core/origin_trials/OriginTrialContext.cpp |
+++ b/third_party/WebKit/Source/core/origin_trials/OriginTrialContext.cpp |
@@ -5,6 +5,7 @@ |
#include "core/origin_trials/OriginTrialContext.h" |
#include "core/dom/ExecutionContext.h" |
+#include "platform/Histogram.h" |
#include "platform/RuntimeEnabledFeatures.h" |
#include "platform/weborigin/SecurityOrigin.h" |
#include "public/platform/Platform.h" |
@@ -15,6 +16,44 @@ namespace blink { |
namespace { |
+// The enum entries below are written to histograms and thus cannot be deleted |
+// or reordered. |
+// New entries must be added immediately before the end. |
+enum EnableResult { |
Marijn Kruisselbrink
2016/04/21 18:18:43
I would make these enum class EnableResult { Succe
chasej
2016/04/22 18:50:06
Yes, it's not ideal to have the separate enum. I c
Marijn Kruisselbrink
2016/04/22 21:51:06
My preference would be to just have one central en
chasej
2016/04/25 20:32:52
As in earlier comment, I've changed to use a centr
|
+ EnableResultSuccess = 0, |
Marijn Kruisselbrink
2016/04/21 18:18:42
Either give all enum members explicit values, or r
chasej
2016/04/22 18:50:06
Done.
|
+ EnableResultNotSupported, |
+ EnableResultInsecure, |
+ EnableResultNoTokens, |
+ EnableResultExpired, |
+ EnableResultWrongFeature, |
+ EnableResultWrongOrigin, |
+ EnableResultInvalidSignature, |
+ EnableResultMalformed, |
+ EnableResultLast = EnableResultMalformed |
+}; |
+ |
+// The enum entries below are written to histograms and thus cannot be deleted |
+// or reordered. |
+// New entries must be added immediately before the end. |
+enum MessageGeneratedResult { |
+ MessageGeneratedResultNotRequested = 0, |
+ MessageGeneratedResultYes, |
+ MessageGeneratedResultNo, |
+ MessageGeneratedResultLast = MessageGeneratedResultNo |
+}; |
+ |
+static EnumerationHistogram& enabledResultHistogram() |
+{ |
+ DEFINE_STATIC_LOCAL(EnumerationHistogram, histogram, ("OriginTrials.FeatureEnabled", EnableResultLast)); |
Marijn Kruisselbrink
2016/04/21 18:18:43
Since we actually want to support trials in worker
chasej
2016/04/22 18:50:06
Done.
|
+ return histogram; |
+} |
+ |
+static EnumerationHistogram& messageGeneratedResultHistogram() |
+{ |
+ DEFINE_STATIC_LOCAL(EnumerationHistogram, histogram, ("OriginTrials.FeatureEnabled.MessageGenerated", MessageGeneratedResultLast)); |
+ return histogram; |
+} |
+ |
String getDisabledMessage(const String& featureName) |
{ |
return "The '" + featureName + "' feature is currently enabled in limited trials. Please see https://bit.ly/OriginTrials for information on enabling a trial for your site."; |
@@ -25,6 +64,83 @@ String getInvalidTokenMessage(const String& featureName) |
return "The provided token(s) are not valid for the '" + featureName + "' feature."; |
} |
+String getNotSupportedMessage() |
+{ |
+ return "This browser does not support origin trials."; |
+} |
+ |
+int getTokenValidationResultPriority( |
+ WebTrialTokenValidator::TokenValidationResult validationResult) |
+{ |
+ switch (validationResult) { |
+ case WebTrialTokenValidator::TokenValidationResultSuccess: |
+ // This function should only be used for validation failures |
+ ASSERT_NOT_REACHED(); |
+ return 99; |
+ case WebTrialTokenValidator::TokenValidationResultExpired: |
+ return 1; |
+ case WebTrialTokenValidator::TokenValidationResultWrongFeature: |
+ return 2; |
+ case WebTrialTokenValidator::TokenValidationResultWrongOrigin: |
+ return 3; |
+ case WebTrialTokenValidator::TokenValidationResultInvalidSignature: |
+ return 4; |
+ case WebTrialTokenValidator::TokenValidationResultMalformed: |
+ return 5; |
+ case WebTrialTokenValidator::TokenValidationResultNotSupported: |
+ return 6; |
+ } |
+ |
+ ASSERT_NOT_REACHED(); |
Marijn Kruisselbrink
2016/04/21 18:18:42
ASSERT_NOT_REACHED() is deprecated. Use NOTREACHED
chasej
2016/04/22 18:50:06
Done.
|
+} |
+ |
+EnableResult mapTokenValidationResultToEnableResult( |
+ WebTrialTokenValidator::TokenValidationResult validationResult) |
+{ |
+ switch (validationResult) { |
+ case WebTrialTokenValidator::TokenValidationResultSuccess: |
+ return EnableResultSuccess; |
+ case WebTrialTokenValidator::TokenValidationResultExpired: |
+ return EnableResultExpired; |
+ case WebTrialTokenValidator::TokenValidationResultWrongFeature: |
+ return EnableResultWrongFeature; |
+ case WebTrialTokenValidator::TokenValidationResultWrongOrigin: |
+ return EnableResultWrongOrigin; |
+ case WebTrialTokenValidator::TokenValidationResultInvalidSignature: |
+ return EnableResultInvalidSignature; |
+ case WebTrialTokenValidator::TokenValidationResultMalformed: |
+ return EnableResultMalformed; |
+ case WebTrialTokenValidator::TokenValidationResultNotSupported: |
+ return EnableResultNotSupported; |
+ } |
+ |
+ ASSERT_NOT_REACHED(); |
+} |
+ |
+WebTrialTokenValidator::TokenValidationResult UpdateResultFromValidationFailure( |
+ WebTrialTokenValidator::TokenValidationResult newResult, |
+ WebTrialTokenValidator::TokenValidationResult currentResult) |
+{ |
+ // There could be zero, one or multiple trial tokens provided for a given |
+ // context, and any number of them could be invalid. The tokens could be |
+ // invalid for a variety of reasons. For multiple invalid tokens, the |
+ // failures are collected into a single result for collecting metrics. At a |
+ // high level, the idea is to report on the tokens that were closest to |
+ // being valid for enabling a feature. This gives the priority as: |
+ // 1. Expired, but otherwise valid |
+ // 2. Wrong feature, but correct origin |
+ // 3. Wrong origin, but correct format for token data |
+ // 4. Invalid signature for token |
+ // 5. Invalid data in token (can be before/after validating signature) |
+ // 6. Embedder does not support origin trials |
+ // See this document for details: |
+ // https://docs.google.com/document/d/1qVP2CK1lbfmtIJRIm6nwuEFFhGhYbtThLQPo3CSTtmg/edit#bookmark=id.k1j0q938so3b |
+ if (currentResult == WebTrialTokenValidator::TokenValidationResultSuccess || getTokenValidationResultPriority(newResult) < getTokenValidationResultPriority(currentResult)) { |
+ return newResult; |
+ } |
+ return currentResult; |
+} |
+ |
} // namespace |
OriginTrialContext::OriginTrialContext(ExecutionContext* host) : m_host(host) |
@@ -56,11 +172,59 @@ void OriginTrialContext::addToken(const String& token) |
bool OriginTrialContext::isFeatureEnabled(const String& featureName, String* errorMessage, WebTrialTokenValidator* validator) |
{ |
if (!RuntimeEnabledFeatures::experimentalFrameworkEnabled()) { |
- // TODO(iclelland): Set an error message here, the first time the |
- // context is accessed in this renderer. |
+ // Do not set an error message. When the framework is disabled, it |
+ // should behave the same as when only runtime flags are used. |
return false; |
} |
+ EnableResult result = static_cast<EnableResult>(checkFeatureEnabled(featureName, errorMessage, validator)); |
+ bool isEnabled = (result == EnableResultSuccess); |
+ |
+ // Record metrics for the enabled result, but only once per context. |
+ if (!m_enabledResultCountedForFeature.contains(featureName)) { |
+ enabledResultHistogram().count(result); |
+ m_enabledResultCountedForFeature.add(featureName); |
+ } |
+ |
+ // If an error message has already been generated in this context, for this |
+ // feature, do not generate another one. This avoids cluttering the console |
+ // with error messages on every attempt to access the feature. |
+ // Metrics are also recorded, to track whether too many messages are being |
+ // generated over time. |
+ MessageGeneratedResult generateMessage = MessageGeneratedResultYes; |
+ if (!errorMessage) { |
+ generateMessage = MessageGeneratedResultNotRequested; |
+ } else if (m_errorMessageGeneratedForFeature.contains(featureName)) { |
+ *errorMessage = ""; |
+ generateMessage = MessageGeneratedResultNo; |
+ } |
+ messageGeneratedResultHistogram().count(result); |
+ |
+ if (generateMessage != MessageGeneratedResultYes) { |
+ return isEnabled; |
+ } |
+ |
+ // Generate an error message, only if one was not already provided by the |
+ // enabled check. |
+ if (!errorMessage->length()) { |
+ switch (result) { |
+ case EnableResultNotSupported: |
+ *errorMessage = getNotSupportedMessage(); |
+ break; |
+ case EnableResultNoTokens: |
+ *errorMessage = getDisabledMessage(featureName); |
+ break; |
+ default: |
+ *errorMessage = getInvalidTokenMessage(featureName); |
Marijn Kruisselbrink
2016/04/21 18:18:43
Would it make sense to refine the error message ba
chasej
2016/04/22 18:50:06
I supposed we could now - before it wasn't possibl
Marijn Kruisselbrink
2016/04/22 21:51:06
sounds good (not sure how bindings changes would e
chasej
2016/04/25 20:32:52
Today, features are still available to JavaScript,
Marijn Kruisselbrink
2016/04/25 21:03:44
Ah, so it's not like bindings changes would elimin
|
+ break; |
+ } |
+ } |
+ m_errorMessageGeneratedForFeature.add(featureName); |
+ return isEnabled; |
+} |
+ |
+int OriginTrialContext::checkFeatureEnabled(const String& featureName, String* errorMessage, WebTrialTokenValidator* validator) |
Marijn Kruisselbrink
2016/04/21 18:18:43
Why is this returning "int" rather than the correc
chasej
2016/04/22 18:50:06
The EnableResult enum is declared local to the sou
Marijn Kruisselbrink
2016/04/22 21:51:06
I think anything that results in having the method
chasej
2016/04/25 20:32:52
The return type is a non-issue, now that I'm using
Marijn Kruisselbrink
2016/04/25 21:03:44
I think personally I would have made the split rou
|
+{ |
// Feature trials are only enabled for secure origins |
bool isSecure = errorMessage |
? m_host->isSecureContext(*errorMessage) |
@@ -71,45 +235,31 @@ bool OriginTrialContext::isFeatureEnabled(const String& featureName, String* err |
// not, and decide whether the OriginTrialContext should be using its |
// own error messages for this case. |
DCHECK(!errorMessage || !errorMessage->isEmpty()); |
- return false; |
+ return EnableResultInsecure; |
} |
if (!validator) { |
validator = Platform::current()->trialTokenValidator(); |
if (!validator) { |
- // TODO(iclelland): Set an error message here, the first time the |
- // context is accessed in this renderer. |
- return false; |
+ return EnableResultNotSupported; |
} |
} |
- |
+ WebTrialTokenValidator::TokenValidationResult failedValidationResult = WebTrialTokenValidator::TokenValidationResultSuccess; |
WebSecurityOrigin origin(m_host->getSecurityOrigin()); |
for (const String& token : m_tokens) { |
- // Check with the validator service to verify the signature. |
- if (validator->validateToken(token, origin, featureName)) { |
- return true; |
+ // Check with the validator service to verify the signature and that |
+ // the token is valid for the combination of origin and feature. |
+ WebTrialTokenValidator::TokenValidationResult tokenResult = validator->validateToken(token, origin, featureName); |
+ if (tokenResult == WebTrialTokenValidator::TokenValidationResultSuccess) { |
+ return EnableResultSuccess; |
} |
+ failedValidationResult = UpdateResultFromValidationFailure(tokenResult, failedValidationResult); |
} |
- if (!errorMessage) |
- return false; |
- |
- // If an error message has already been generated in this context, for this |
- // feature, do not generate another one. (This avoids cluttering the console |
- // with error messages on every attempt to access the feature.) |
- if (m_errorMessageGeneratedForFeature.contains(featureName)) { |
- *errorMessage = ""; |
- return false; |
- } |
- |
- if (m_tokens.size()) { |
- *errorMessage = getInvalidTokenMessage(featureName); |
- } else { |
- *errorMessage = getDisabledMessage(featureName); |
- } |
- m_errorMessageGeneratedForFeature.add(featureName); |
- return false; |
+ return m_tokens.size() |
+ ? mapTokenValidationResultToEnableResult(failedValidationResult) |
+ : EnableResultNoTokens; |
} |
DEFINE_TRACE(OriginTrialContext) |