blob: 1649a3cff9947f47f38385e369b054f7dea07b3f [file] [log] [blame]
// Copyright 2017 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.
#include "chrome/browser/page_load_metrics/observers/use_counter_page_load_metrics_observer.h"
#include "chrome/browser/browser_process.h"
#include "content/public/browser/render_frame_host.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
using Features = page_load_metrics::mojom::PageLoadFeatures;
using UkmFeatureList = UseCounterPageLoadMetricsObserver::UkmFeatureList;
using WebFeature = blink::mojom::WebFeature;
using WebFeatureBitSet =
std::bitset<static_cast<size_t>(WebFeature::kNumberOfFeatures)>;
using CSSSampleId = int32_t;
namespace {
void RecordUkmFeatures(const UkmFeatureList& features,
const WebFeatureBitSet& features_recorded,
const WebFeatureBitSet& main_frame_features_recorded,
std::set<size_t>* ukm_features_recorded,
ukm::SourceId source_id) {
for (auto feature : features) {
if (!features_recorded.test(static_cast<size_t>(feature)))
continue;
if (ukm_features_recorded->find(static_cast<size_t>(feature)) !=
ukm_features_recorded->end())
continue;
// TODO(kochi): https://crbug.com/806671 https://843080
// as ElementCreateShadowRoot is ~8% and
// DocumentRegisterElement is ~5% as of May, 2018, to meet UKM's data
// volume expectation, reduce the data size by sampling. Revisit and
// remove this code once Shadow DOM V0 and Custom Elements V0 are removed.
const int kSamplingFactor = 10;
if ((feature == WebFeature::kElementCreateShadowRoot ||
feature == WebFeature::kDocumentRegisterElement) &&
base::RandGenerator(kSamplingFactor) != 0)
continue;
ukm::builders::Blink_UseCounter(source_id)
.SetFeature(static_cast<size_t>(feature))
.SetIsMainFrameFeature(
main_frame_features_recorded.test(static_cast<size_t>(feature)))
.Record(ukm::UkmRecorder::Get());
ukm_features_recorded->insert(static_cast<size_t>(feature));
}
}
// It's always recommended to use the deprecation API in blink. If the feature
// was logged from the browser (or from both blink and the browser) where the
// deprecation API is not available, use this method for the console warning.
// Note that this doesn't generate the deprecation report.
void PossiblyWarnFeatureDeprecation(content::RenderFrameHost* rfh,
WebFeature feature) {
switch (feature) {
case WebFeature::kNavigationDownloadInSandboxWithoutUserGesture:
case WebFeature::kHTMLAnchorElementDownloadInSandboxWithoutUserGesture:
rfh->AddMessageToConsole(
content::CONSOLE_MESSAGE_LEVEL_WARNING,
"Download in sandbox without user activation is deprecated and will "
"be removed in M74. You may consider adding "
"'allow-downloads-without-user-activation' to the sandbox attribute "
"list. See https://www.chromestatus.com/feature/5706745674465280 for "
"more details.");
return;
default:
return;
}
}
void RecordMainFrameFeature(blink::mojom::WebFeature feature) {
UMA_HISTOGRAM_ENUMERATION(internal::kFeaturesHistogramMainFrameName, feature);
}
void RecordFeature(blink::mojom::WebFeature feature) {
UMA_HISTOGRAM_ENUMERATION(internal::kFeaturesHistogramName, feature);
}
void RecordCssProperty(CSSSampleId property) {
UMA_HISTOGRAM_ENUMERATION(internal::kCssPropertiesHistogramName, property,
blink::mojom::kMaximumCSSSampleId);
}
void RecordAnimatedCssProperty(CSSSampleId animated_property) {
UMA_HISTOGRAM_ENUMERATION(internal::kAnimatedCssPropertiesHistogramName,
animated_property,
blink::mojom::kMaximumCSSSampleId);
}
} // namespace
UseCounterPageLoadMetricsObserver::UseCounterPageLoadMetricsObserver() {}
UseCounterPageLoadMetricsObserver::~UseCounterPageLoadMetricsObserver() {}
page_load_metrics::PageLoadMetricsObserver::ObservePolicy
UseCounterPageLoadMetricsObserver::OnCommit(
content::NavigationHandle* navigation_handle,
ukm::SourceId source_id) {
// Verify that no feature usage is observed before commit
DCHECK_LE(features_recorded_.count(), 0ul);
DCHECK_LE(main_frame_features_recorded_.count(), 0ul);
ukm::builders::Blink_UseCounter(source_id)
.SetFeature(static_cast<size_t>(WebFeature::kPageVisits))
.SetIsMainFrameFeature(1)
.Record(ukm::UkmRecorder::Get());
ukm_features_recorded_.insert(static_cast<size_t>(WebFeature::kPageVisits));
RecordFeature(WebFeature::kPageVisits);
RecordMainFrameFeature(WebFeature::kPageVisits);
RecordCssProperty(blink::mojom::kTotalPagesMeasuredCSSSampleId);
RecordAnimatedCssProperty(blink::mojom::kTotalPagesMeasuredCSSSampleId);
features_recorded_.set(static_cast<size_t>(WebFeature::kPageVisits));
main_frame_features_recorded_.set(
static_cast<size_t>(WebFeature::kPageVisits));
return CONTINUE_OBSERVING;
}
void UseCounterPageLoadMetricsObserver::OnFeaturesUsageObserved(
content::RenderFrameHost* rfh,
const Features& features,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
for (WebFeature feature : features.features) {
// Verify that kPageVisits is observed at most once per observer.
if (feature == WebFeature::kPageVisits) {
mojo::ReportBadMessage(
"kPageVisits should not be passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
// Record feature usage in main frame.
// If a feature is already recorded in the main frame, it is also recorded
// on the page.
if (main_frame_features_recorded_.test(static_cast<size_t>(feature)))
continue;
if (rfh->GetParent() == nullptr) {
RecordMainFrameFeature(feature);
main_frame_features_recorded_.set(static_cast<size_t>(feature));
}
if (features_recorded_.test(static_cast<size_t>(feature)))
continue;
PossiblyWarnFeatureDeprecation(rfh, feature);
RecordFeature(feature);
features_recorded_.set(static_cast<size_t>(feature));
}
for (int css_property : features.css_properties) {
// Verify that page visit is observed at most once per observer.
if (css_property == blink::mojom::kTotalPagesMeasuredCSSSampleId) {
mojo::ReportBadMessage(
"kTotalPagesMeasuredCSSSampleId should not be passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
if (css_property > blink::mojom::kMaximumCSSSampleId) {
mojo::ReportBadMessage(
"Invalid CSS property passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
// Same as above, the usage of each CSS property should be only measured
// once.
if (css_properties_recorded_.test(css_property))
continue;
// There are about 600 enums, so the memory required for a vector histogram
// is about 600 * 8 byes = 5KB
// 50% of the time there are about 100 CSS properties recorded per page
// load. Storage in sparce histogram entries are 48 bytes instead of 8
// bytes so the memory required for a sparse histogram is about
// 100 * 48 bytes = 5KB. On top there will be std::map overhead and the
// acquire/release of a base::Lock to protect the map during each update.
// Overal it is still better to use a vector histogram here since it is
// faster to access and merge and uses about same amount of memory.
RecordCssProperty(css_property);
css_properties_recorded_.set(css_property);
}
for (int animated_css_property : features.animated_css_properties) {
// Verify that page visit is observed at most once per observer.
if (animated_css_property == blink::mojom::kTotalPagesMeasuredCSSSampleId) {
mojo::ReportBadMessage(
"kTotalPagesMeasuredCSSSampleId should not be passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
if (animated_css_property > blink::mojom::kMaximumCSSSampleId) {
mojo::ReportBadMessage(
"Invalid animated CSS property passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
// Same as above, the usage of each animated CSS property should be only
// measured once.
if (animated_css_properties_recorded_.test(animated_css_property))
continue;
// See comments above (in the css property section) for reasoning of using
// a vector histogram here instead of a sparse histogram.
RecordAnimatedCssProperty(animated_css_property);
animated_css_properties_recorded_.set(animated_css_property);
}
}
void UseCounterPageLoadMetricsObserver::OnComplete(
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
RecordUkmFeatures(GetAllowedUkmFeatures(), features_recorded_,
main_frame_features_recorded_, &ukm_features_recorded_,
extra_info.source_id);
}
void UseCounterPageLoadMetricsObserver::OnFailedProvisionalLoad(
const page_load_metrics::FailedProvisionalLoadInfo&
failed_provisional_load_info,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
RecordUkmFeatures(GetAllowedUkmFeatures(), features_recorded_,
main_frame_features_recorded_, &ukm_features_recorded_,
extra_info.source_id);
}
page_load_metrics::PageLoadMetricsObserver::ObservePolicy
UseCounterPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
const page_load_metrics::mojom::PageLoadTiming& timing,
const page_load_metrics::PageLoadExtraInfo& extra_info) {
RecordUkmFeatures(GetAllowedUkmFeatures(), features_recorded_,
main_frame_features_recorded_, &ukm_features_recorded_,
extra_info.source_id);
return CONTINUE_OBSERVING;
}
page_load_metrics::PageLoadMetricsObserver::ObservePolicy
UseCounterPageLoadMetricsObserver::ShouldObserveMimeType(
const std::string& mime_type) const {
return PageLoadMetricsObserver::ShouldObserveMimeType(mime_type) ==
CONTINUE_OBSERVING ||
mime_type == "image/svg+xml"
? CONTINUE_OBSERVING
: STOP_OBSERVING;
}