blob: c11215f0106a7f3c1244a7af78fb12807847e9d7 [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 "components/page_load_metrics/browser/observers/use_counter_page_load_metrics_observer.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "content/public/browser/render_frame_host.h"
#include "services/metrics/public/cpp/ukm_builders.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 = blink::mojom::CSSSampleId;
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::kDownloadInSandbox:
rfh->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
"Download is disallowed. The frame initiating or instantiating the "
"download is sandboxed, but the flag ‘allow-downloads’ is not set. "
"See https://www.chromestatus.com/feature/5706745674465280 for more "
"details.");
return;
case WebFeature::kDownloadInAdFrameWithoutUserGesture:
rfh->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kWarning,
"[Intervention] Download in ad frame without user activation is "
"not allowed. See "
"https://www.chromestatus.com/feature/6311883621531648 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);
}
void RecordAnimatedCssProperty(CSSSampleId animated_property) {
UMA_HISTOGRAM_ENUMERATION(internal::kAnimatedCssPropertiesHistogramName,
animated_property);
}
} // namespace
UseCounterPageLoadMetricsObserver::UseCounterPageLoadMetricsObserver() =
default;
UseCounterPageLoadMetricsObserver::~UseCounterPageLoadMetricsObserver() =
default;
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(CSSSampleId::kTotalPagesMeasured);
RecordAnimatedCssProperty(CSSSampleId::kTotalPagesMeasured);
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) {
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 (CSSSampleId css_property : features.css_properties) {
// Verify that page visit is observed at most once per observer.
if (css_property == CSSSampleId::kTotalPagesMeasured) {
mojo::ReportBadMessage(
"CSSSampleId::kTotalPagesMeasured should not be passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
if (css_property > CSSSampleId::kMaxValue) {
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(static_cast<size_t>(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(static_cast<size_t>(css_property));
}
for (CSSSampleId animated_css_property : features.animated_css_properties) {
// Verify that page visit is observed at most once per observer.
if (animated_css_property ==
blink::mojom::CSSSampleId::kTotalPagesMeasured) {
mojo::ReportBadMessage(
"CSSSampleId::kTotalPagesMeasured should not be passed to "
"PageLoadMetricsObserver::OnFeaturesUsageObserved");
return;
}
if (animated_css_property > blink::mojom::CSSSampleId::kMaxValue) {
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(
static_cast<size_t>(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(
static_cast<size_t>(animated_css_property));
}
}
void UseCounterPageLoadMetricsObserver::OnComplete(
const page_load_metrics::mojom::PageLoadTiming& timing) {
RecordUkmFeatures(GetAllowedUkmFeatures(), features_recorded_,
main_frame_features_recorded_, &ukm_features_recorded_,
GetDelegate().GetSourceId());
}
void UseCounterPageLoadMetricsObserver::OnFailedProvisionalLoad(
const page_load_metrics::FailedProvisionalLoadInfo&
failed_provisional_load_info) {
RecordUkmFeatures(GetAllowedUkmFeatures(), features_recorded_,
main_frame_features_recorded_, &ukm_features_recorded_,
GetDelegate().GetSourceId());
}
page_load_metrics::PageLoadMetricsObserver::ObservePolicy
UseCounterPageLoadMetricsObserver::FlushMetricsOnAppEnterBackground(
const page_load_metrics::mojom::PageLoadTiming& timing) {
RecordUkmFeatures(GetAllowedUkmFeatures(), features_recorded_,
main_frame_features_recorded_, &ukm_features_recorded_,
GetDelegate().GetSourceId());
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;
}