blob: 45434e4e8c262b5f61166009d16099d72ee8e070 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/public/common/page/content_to_visible_time_reporter.h"
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/debug/dump_without_crashing.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/trace_event/trace_event.h"
#include "third_party/blink/public/mojom/page/record_content_to_visible_time_request.mojom.h"
#include "ui/gfx/presentation_feedback.h"
namespace blink {
namespace {
// Used to generate unique "TabSwitching::Latency" event ids. Note: The address
// of ContentToVisibleTimeReporter can't be used as an id because a single
// ContentToVisibleTimeReporter can generate multiple overlapping events.
int g_num_trace_events_in_process = 0;
const char* GetHistogramSuffix(
bool has_saved_frames,
const mojom::RecordContentToVisibleTimeRequest& start_state) {
if (has_saved_frames)
return "WithSavedFrames";
if (start_state.destination_is_loaded) {
return "NoSavedFrames_Loaded";
} else {
return "NoSavedFrames_NotLoaded";
}
}
void ReportUnOccludedMetric(const base::TimeTicks requested_time,
const gfx::PresentationFeedback& feedback) {
const base::TimeDelta delta = feedback.timestamp - requested_time;
UMA_HISTOGRAM_TIMES("Aura.WebContentsWindowUnOccludedTime", delta);
}
void RecordBackForwardCacheRestoreMetric(
const base::TimeTicks requested_time,
const gfx::PresentationFeedback& feedback) {
const base::TimeDelta delta = feedback.timestamp - requested_time;
// Histogram to record the content to visible duration after restoring a page
// from back-forward cache. Here min, max bucket size are same as the
// "PageLoad.PaintTiming.NavigationToFirstContentfulPaint" metric.
base::UmaHistogramCustomTimes(
"BackForwardCache.Restore.NavigationToFirstPaint", delta,
base::TimeDelta::FromMilliseconds(10), base::TimeDelta::FromMinutes(10),
100);
}
} // namespace
void UpdateRecordContentToVisibleTimeRequest(
mojom::RecordContentToVisibleTimeRequest const& from,
mojom::RecordContentToVisibleTimeRequest& to) {
to.event_start_time = std::min(to.event_start_time, from.event_start_time);
to.destination_is_loaded |= from.destination_is_loaded;
to.show_reason_tab_switching |= from.show_reason_tab_switching;
to.show_reason_unoccluded |= from.show_reason_unoccluded;
to.show_reason_bfcache_restore |= from.show_reason_bfcache_restore;
}
ContentToVisibleTimeReporter::ContentToVisibleTimeReporter() = default;
ContentToVisibleTimeReporter::~ContentToVisibleTimeReporter() = default;
base::OnceCallback<void(const gfx::PresentationFeedback&)>
ContentToVisibleTimeReporter::TabWasShown(
bool has_saved_frames,
mojom::RecordContentToVisibleTimeRequestPtr start_state,
base::TimeTicks widget_visibility_request_timestamp) {
DCHECK(!start_state->event_start_time.is_null());
DCHECK(!widget_visibility_request_timestamp.is_null());
DCHECK(!tab_switch_start_state_);
DCHECK(widget_visibility_request_timestamp_.is_null());
// Invalidate previously issued callbacks, to avoid accessing a null
// |tab_switch_start_state_|.
//
// TODO(https://crbug.com/1121339): Make sure that TabWasShown() is never
// called twice without a call to TabWasHidden() in-between, and remove this
// mitigation.
weak_ptr_factory_.InvalidateWeakPtrs();
has_saved_frames_ = has_saved_frames;
tab_switch_start_state_ = std::move(start_state);
widget_visibility_request_timestamp_ = widget_visibility_request_timestamp;
// |tab_switch_start_state_| is only reset by RecordHistogramsAndTraceEvents
// once the metrics have been emitted.
return base::BindOnce(
&ContentToVisibleTimeReporter::RecordHistogramsAndTraceEvents,
weak_ptr_factory_.GetWeakPtr(), false /* is_incomplete */,
tab_switch_start_state_->show_reason_tab_switching,
tab_switch_start_state_->show_reason_unoccluded,
tab_switch_start_state_->show_reason_bfcache_restore);
}
base::OnceCallback<void(const gfx::PresentationFeedback&)>
ContentToVisibleTimeReporter::TabWasShown(
bool has_saved_frames,
base::TimeTicks event_start_time,
bool destination_is_loaded,
bool show_reason_tab_switching,
bool show_reason_unoccluded,
bool show_reason_bfcache_restore,
base::TimeTicks widget_visibility_request_timestamp) {
return TabWasShown(
has_saved_frames,
mojom::RecordContentToVisibleTimeRequest::New(
event_start_time, destination_is_loaded, show_reason_tab_switching,
show_reason_unoccluded, show_reason_bfcache_restore),
widget_visibility_request_timestamp);
}
void ContentToVisibleTimeReporter::TabWasHidden() {
if (tab_switch_start_state_) {
RecordHistogramsAndTraceEvents(true /* is_incomplete */,
true /* show_reason_tab_switching */,
false /* show_reason_unoccluded */,
false /* show_reason_bfcache_restore */,
gfx::PresentationFeedback::Failure());
weak_ptr_factory_.InvalidateWeakPtrs();
}
}
void ContentToVisibleTimeReporter::RecordHistogramsAndTraceEvents(
bool is_incomplete,
bool show_reason_tab_switching,
bool show_reason_unoccluded,
bool show_reason_bfcache_restore,
const gfx::PresentationFeedback& feedback) {
DCHECK(tab_switch_start_state_);
DCHECK(!widget_visibility_request_timestamp_.is_null());
// If the DCHECK fail, make sure RenderWidgetHostImpl::WasShown was triggered
// for recording the event.
DCHECK(show_reason_bfcache_restore || show_reason_unoccluded ||
show_reason_tab_switching);
if (show_reason_bfcache_restore) {
RecordBackForwardCacheRestoreMetric(
tab_switch_start_state_->event_start_time, feedback);
}
if (show_reason_unoccluded) {
ReportUnOccludedMetric(tab_switch_start_state_->event_start_time, feedback);
}
if (!show_reason_tab_switching)
return;
// Tab switching has occurred.
auto tab_switch_result = TabSwitchResult::kSuccess;
if (is_incomplete)
tab_switch_result = TabSwitchResult::kIncomplete;
else if (feedback.flags & gfx::PresentationFeedback::kFailure)
tab_switch_result = TabSwitchResult::kPresentationFailure;
const auto tab_switch_duration =
feedback.timestamp - tab_switch_start_state_->event_start_time;
// Record trace events.
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"latency", "TabSwitching::Latency",
TRACE_ID_LOCAL(g_num_trace_events_in_process),
tab_switch_start_state_->event_start_time);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP2(
"latency", "TabSwitching::Latency",
TRACE_ID_LOCAL(g_num_trace_events_in_process), feedback.timestamp,
"result", tab_switch_result, "latency",
tab_switch_duration.InMillisecondsF());
++g_num_trace_events_in_process;
// Record result histogram.
base::UmaHistogramEnumeration(
std::string("Browser.Tabs.TabSwitchResult.") +
GetHistogramSuffix(has_saved_frames_, *tab_switch_start_state_),
tab_switch_result);
// Record latency histogram.
switch (tab_switch_result) {
case TabSwitchResult::kSuccess: {
base::UmaHistogramTimes(
std::string("Browser.Tabs.TotalSwitchDuration.") +
GetHistogramSuffix(has_saved_frames_, *tab_switch_start_state_),
tab_switch_duration);
break;
}
case TabSwitchResult::kIncomplete: {
base::UmaHistogramTimes(
std::string("Browser.Tabs.TotalIncompleteSwitchDuration.") +
GetHistogramSuffix(has_saved_frames_, *tab_switch_start_state_),
tab_switch_duration);
break;
}
case TabSwitchResult::kPresentationFailure: {
break;
}
}
// Record legacy latency histogram.
UMA_HISTOGRAM_TIMES(
"MPArch.RWH_TabSwitchPaintDuration",
feedback.timestamp - widget_visibility_request_timestamp_);
// Reset tab switch information.
has_saved_frames_ = false;
tab_switch_start_state_.reset();
widget_visibility_request_timestamp_ = base::TimeTicks();
}
} // namespace blink