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..11185e6aac4ea1f15edf83b047e93bfe2f6ee840 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 { |
| + EnableResultSuccess = 0, |
| + EnableResultNotSupported = 1, |
| + EnableResultInsecure = 2, |
| + EnableResultNoTokens = 3, |
| + EnableResultExpired = 4, |
| + EnableResultWrongFeature = 5, |
| + EnableResultWrongOrigin = 6, |
| + EnableResultInvalidSignature = 7, |
| + EnableResultMalformed = 8, |
| + 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 = 1, |
| + MessageGeneratedResultNo = 2, |
| + MessageGeneratedResultLast = MessageGeneratedResultNo |
| +}; |
| + |
| +static EnumerationHistogram& enabledResultHistogram() |
| +{ |
| + DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, histogram, new EnumerationHistogram("OriginTrials.FeatureEnabled", EnableResultLast)); |
| + return histogram; |
| +} |
| + |
| +static EnumerationHistogram& messageGeneratedResultHistogram() |
| +{ |
| + DEFINE_THREAD_SAFE_STATIC_LOCAL(EnumerationHistogram, histogram, new EnumerationHistogram("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,85 @@ 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 |
| + NOTREACHED(); |
| + 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; |
| + } |
| + |
| + NOTREACHED(); |
| + return 99; |
| +} |
| + |
| +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; |
| + } |
| + |
| + NOTREACHED(); |
| + return EnableResultSuccess; |
| +} |
| + |
| +WebTrialTokenValidator::TokenValidationResult UpdateResultFromValidationFailure( |
| + WebTrialTokenValidator::TokenValidationResult newResult, |
| + WebTrialTokenValidator::TokenValidationResult currentResult) |
| +{ |
| + // There could be zero, one or multiple trial tokens provided for a given |
|
Marijn Kruisselbrink
2016/04/22 21:51:06
I wonder if this comment should maybe be in the ge
chasej
2016/04/25 20:32:52
Done.
|
| + // 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 +174,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); |
| + break; |
| + } |
| + } |
| + m_errorMessageGeneratedForFeature.add(featureName); |
| + return isEnabled; |
| +} |
| + |
| +int OriginTrialContext::checkFeatureEnabled(const String& featureName, String* errorMessage, WebTrialTokenValidator* validator) |
| +{ |
| // Feature trials are only enabled for secure origins |
| bool isSecure = errorMessage |
| ? m_host->isSecureContext(*errorMessage) |
| @@ -71,45 +237,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) |