blob: 394387eb4784859cb54cbbceac5ceca16ec77abf [file] [log] [blame]
// Copyright 2016 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/renderer/core/html/media/autoplay_uma_helper.h"
#include "base/metrics/histogram_functions.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/media/autoplay_policy.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer.h"
#include "third_party/blink/renderer/core/intersection_observer/intersection_observer_entry.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/network/network_state_notifier.h"
namespace blink {
namespace {
constexpr base::TimeDelta kMaxOffscreenDurationUma =
base::TimeDelta::FromHours(1);
constexpr int32_t kOffscreenDurationUmaBucketCount = 50;
// Returns a int64_t with the following structure:
// 0b0001 set if there is a user gesture on the stack.
// 0b0010 set if there was a user gesture on the page.
// 0b0100 set if there was a user gesture propagated after navigation.
int64_t GetUserGestureStatusForUkmMetric(LocalFrame* frame) {
DCHECK(frame);
int64_t result = 0;
if (LocalFrame::HasTransientUserActivation(frame))
result |= 0x01;
if (frame->HasStickyUserActivation())
result |= 0x02;
if (frame->HadStickyUserActivationBeforeNavigation())
result |= 0x04;
return result;
}
} // namespace
AutoplayUmaHelper::AutoplayUmaHelper(HTMLMediaElement* element)
: ExecutionContextLifecycleObserver(
static_cast<ExecutionContext*>(nullptr)),
element_(element),
muted_video_play_method_intersection_observer_(nullptr),
is_visible_(false),
muted_video_offscreen_duration_intersection_observer_(nullptr) {}
AutoplayUmaHelper::~AutoplayUmaHelper() = default;
static void RecordAutoplaySourceMetrics(HTMLMediaElement* element,
AutoplaySource source) {
if (IsA<HTMLVideoElement>(element)) {
base::UmaHistogramEnumeration("Media.Video.Autoplay", source);
if (element->muted())
base::UmaHistogramEnumeration("Media.Video.Autoplay.Muted", source);
return;
}
base::UmaHistogramEnumeration("Media.Audio.Autoplay", source);
}
void AutoplayUmaHelper::OnAutoplayInitiated(AutoplaySource source) {
// Autoplay already initiated
if (sources_.Contains(source))
return;
sources_.insert(source);
// Record the source.
RecordAutoplaySourceMetrics(element_.Get(), source);
// Record dual source.
if (sources_.size() == kDualSourceSize)
RecordAutoplaySourceMetrics(element_.Get(), AutoplaySource::kDualSource);
element_->addEventListener(event_type_names::kPlaying, this, false);
// Record UKM autoplay event.
if (!element_->GetDocument().IsActive())
return;
LocalFrame* frame = element_->GetDocument().GetFrame();
DCHECK(frame);
DCHECK(element_->GetDocument().GetPage());
ukm::UkmRecorder* ukm_recorder = element_->GetDocument().UkmRecorder();
DCHECK(ukm_recorder);
ukm::builders::Media_Autoplay_Attempt(element_->GetDocument().UkmSourceID())
.SetSource(source == AutoplaySource::kMethod)
.SetAudioTrack(element_->HasAudio())
.SetVideoTrack(element_->HasVideo())
.SetUserGestureRequired(
element_->GetAutoplayPolicy().IsGestureNeededForPlayback())
.SetMuted(element_->muted())
.SetHighMediaEngagement(AutoplayPolicy::DocumentHasHighMediaEngagement(
element_->GetDocument()))
.SetUserGestureStatus(GetUserGestureStatusForUkmMetric(frame))
.Record(ukm_recorder);
}
void AutoplayUmaHelper::RecordAutoplayUnmuteStatus(
AutoplayUnmuteActionStatus status) {
base::UmaHistogramEnumeration("Media.Video.Autoplay.Muted.UnmuteAction",
status);
// Record UKM event for unmute muted autoplay.
if (element_->GetDocument().IsInMainFrame()) {
int source = static_cast<int>(AutoplaySource::kAttribute);
if (sources_.size() == kDualSourceSize) {
source = static_cast<int>(AutoplaySource::kDualSource);
} else if (sources_.Contains(AutoplaySource::kMethod)) {
source = static_cast<int>(AutoplaySource::kAttribute);
}
ukm::UkmRecorder* ukm_recorder = element_->GetDocument().UkmRecorder();
DCHECK(ukm_recorder);
ukm::builders::Media_Autoplay_Muted_UnmuteAction(
element_->GetDocument().UkmSourceID())
.SetSource(source)
.SetResult(status == AutoplayUnmuteActionStatus::kSuccess)
.Record(ukm_recorder);
}
}
void AutoplayUmaHelper::VideoWillBeDrawnToCanvas() {
if (HasSource() && !IsVisible()) {
UseCounter::Count(element_->GetDocument(),
WebFeature::kHiddenAutoplayedVideoInCanvas);
}
}
void AutoplayUmaHelper::DidMoveToNewDocument(Document& old_document) {
if (!ShouldListenToContextDestroyed())
return;
SetExecutionContext(element_->GetExecutionContext());
}
void AutoplayUmaHelper::
OnIntersectionChangedForMutedVideoPlayMethodBecomeVisible(
const HeapVector<Member<IntersectionObserverEntry>>& entries) {
bool is_visible = (entries.back()->intersectionRatio() > 0);
if (!is_visible || !muted_video_play_method_intersection_observer_)
return;
MaybeStopRecordingMutedVideoPlayMethodBecomeVisible(true);
}
void AutoplayUmaHelper::OnIntersectionChangedForMutedVideoOffscreenDuration(
const HeapVector<Member<IntersectionObserverEntry>>& entries) {
bool is_visible = (entries.back()->intersectionRatio() > 0);
if (is_visible == is_visible_)
return;
if (is_visible) {
muted_video_autoplay_offscreen_duration_ +=
base::TimeTicks::Now() - muted_video_autoplay_offscreen_start_time_;
} else {
muted_video_autoplay_offscreen_start_time_ = base::TimeTicks::Now();
}
is_visible_ = is_visible;
}
void AutoplayUmaHelper::Invoke(ExecutionContext* execution_context,
Event* event) {
if (event->type() == event_type_names::kPlaying)
HandlePlayingEvent();
else if (event->type() == event_type_names::kPause)
HandlePauseEvent();
else
NOTREACHED();
}
void AutoplayUmaHelper::HandlePlayingEvent() {
MaybeStartRecordingMutedVideoPlayMethodBecomeVisible();
MaybeStartRecordingMutedVideoOffscreenDuration();
element_->removeEventListener(event_type_names::kPlaying, this, false);
}
void AutoplayUmaHelper::HandlePauseEvent() {
MaybeStopRecordingMutedVideoOffscreenDuration();
}
void AutoplayUmaHelper::ContextDestroyed() {
HandleContextDestroyed();
}
void AutoplayUmaHelper::HandleContextDestroyed() {
MaybeStopRecordingMutedVideoPlayMethodBecomeVisible(false);
MaybeStopRecordingMutedVideoOffscreenDuration();
}
void AutoplayUmaHelper::MaybeStartRecordingMutedVideoPlayMethodBecomeVisible() {
if (!sources_.Contains(AutoplaySource::kMethod) ||
!IsA<HTMLVideoElement>(element_.Get()) || !element_->muted())
return;
muted_video_play_method_intersection_observer_ = IntersectionObserver::Create(
{}, {IntersectionObserver::kMinimumThreshold}, &element_->GetDocument(),
WTF::BindRepeating(
&AutoplayUmaHelper::
OnIntersectionChangedForMutedVideoPlayMethodBecomeVisible,
WrapWeakPersistent(this)),
LocalFrameUkmAggregator::kMediaIntersectionObserver);
muted_video_play_method_intersection_observer_->observe(element_);
SetExecutionContext(element_->GetExecutionContext());
}
void AutoplayUmaHelper::MaybeStopRecordingMutedVideoPlayMethodBecomeVisible(
bool visible) {
if (!muted_video_play_method_intersection_observer_)
return;
base::UmaHistogramBoolean(
"Media.Video.Autoplay.Muted.PlayMethod.BecomesVisible", visible);
muted_video_play_method_intersection_observer_->disconnect();
muted_video_play_method_intersection_observer_ = nullptr;
MaybeUnregisterContextDestroyedObserver();
}
void AutoplayUmaHelper::MaybeStartRecordingMutedVideoOffscreenDuration() {
if (!IsA<HTMLVideoElement>(element_.Get()) || !element_->muted() ||
!sources_.Contains(AutoplaySource::kMethod))
return;
// Start recording muted video playing offscreen duration.
muted_video_autoplay_offscreen_start_time_ = base::TimeTicks::Now();
is_visible_ = false;
muted_video_offscreen_duration_intersection_observer_ =
IntersectionObserver::Create(
{}, {IntersectionObserver::kMinimumThreshold},
&element_->GetDocument(),
WTF::BindRepeating(
&AutoplayUmaHelper::
OnIntersectionChangedForMutedVideoOffscreenDuration,
WrapWeakPersistent(this)),
LocalFrameUkmAggregator::kMediaIntersectionObserver);
muted_video_offscreen_duration_intersection_observer_->observe(element_);
element_->addEventListener(event_type_names::kPause, this, false);
SetExecutionContext(element_->GetExecutionContext());
}
void AutoplayUmaHelper::MaybeStopRecordingMutedVideoOffscreenDuration() {
if (!muted_video_offscreen_duration_intersection_observer_)
return;
if (!is_visible_) {
muted_video_autoplay_offscreen_duration_ +=
base::TimeTicks::Now() - muted_video_autoplay_offscreen_start_time_;
}
DCHECK(sources_.Contains(AutoplaySource::kMethod));
UMA_HISTOGRAM_CUSTOM_TIMES(
"Media.Video.Autoplay.Muted.PlayMethod.OffscreenDuration",
muted_video_autoplay_offscreen_duration_,
base::TimeDelta::FromMilliseconds(1), kMaxOffscreenDurationUma,
kOffscreenDurationUmaBucketCount);
muted_video_offscreen_duration_intersection_observer_->disconnect();
muted_video_offscreen_duration_intersection_observer_ = nullptr;
muted_video_autoplay_offscreen_duration_ = base::TimeDelta();
MaybeUnregisterMediaElementPauseListener();
MaybeUnregisterContextDestroyedObserver();
}
void AutoplayUmaHelper::MaybeUnregisterContextDestroyedObserver() {
// TODO(keishi): Remove IsIteratingOverObservers() check when
// HeapObserverSet() supports removal while iterating.
if (!ShouldListenToContextDestroyed() && !GetExecutionContext()
->ContextLifecycleObserverSet()
.IsIteratingOverObservers()) {
SetExecutionContext(nullptr);
}
}
void AutoplayUmaHelper::MaybeUnregisterMediaElementPauseListener() {
if (muted_video_offscreen_duration_intersection_observer_)
return;
element_->removeEventListener(event_type_names::kPause, this, false);
}
bool AutoplayUmaHelper::ShouldListenToContextDestroyed() const {
return muted_video_play_method_intersection_observer_ ||
muted_video_offscreen_duration_intersection_observer_;
}
void AutoplayUmaHelper::Trace(Visitor* visitor) const {
NativeEventListener::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor);
visitor->Trace(element_);
visitor->Trace(muted_video_play_method_intersection_observer_);
visitor->Trace(muted_video_offscreen_duration_intersection_observer_);
}
} // namespace blink