Chromium Code Reviews| 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) |