| Index: third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp
|
| diff --git a/third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp b/third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..68dff512c7606574427e3ad58f376d98088a7b5c
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp
|
| @@ -0,0 +1,301 @@
|
| +// 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 "core/html/media/AutoplayPolicy.h"
|
| +
|
| +#include "core/dom/Document.h"
|
| +#include "core/dom/ElementVisibilityObserver.h"
|
| +#include "core/frame/ContentSettingsClient.h"
|
| +#include "core/frame/LocalFrame.h"
|
| +#include "core/frame/Settings.h"
|
| +#include "core/html/HTMLMediaElement.h"
|
| +#include "core/html/media/AutoplayUmaHelper.h"
|
| +#include "platform/RuntimeEnabledFeatures.h"
|
| +#include "platform/UserGestureIndicator.h"
|
| +#include "public/platform/WebMediaPlayer.h"
|
| +
|
| +namespace blink {
|
| +
|
| +namespace {
|
| +
|
| +bool IsDocumentCrossOrigin(Document& document) {
|
| + const LocalFrame* frame = document.GetFrame();
|
| + return frame && frame->IsCrossOriginSubframe();
|
| +}
|
| +
|
| +// Returns whether |document| is whitelisted for autoplay. If true, the user
|
| +// gesture lock will be initilized as false, indicating that the element is
|
| +// allowed to autoplay unmuted without user gesture.
|
| +bool IsDocumentWhitelisted(Document& document) {
|
| + DCHECK(document.GetSettings());
|
| +
|
| + const String& whitelist_scope =
|
| + document.GetSettings()->GetMediaPlaybackGestureWhitelistScope();
|
| + if (whitelist_scope.IsNull() || whitelist_scope.IsEmpty())
|
| + return false;
|
| +
|
| + return document.Url().GetString().StartsWith(whitelist_scope);
|
| +}
|
| +
|
| +// Return true if and only if the document settings specifies media playback
|
| +// requires user gesture.
|
| +bool ComputeLockedPendingUserGesture(Document& document) {
|
| + if (!document.GetSettings())
|
| + return false;
|
| +
|
| + if (IsDocumentWhitelisted(document)) {
|
| + return false;
|
| + }
|
| +
|
| + if (document.GetSettings()
|
| + ->GetCrossOriginMediaPlaybackRequiresUserGesture() &&
|
| + IsDocumentCrossOrigin(document)) {
|
| + return true;
|
| + }
|
| +
|
| + return document.GetSettings()->GetMediaPlaybackRequiresUserGesture();
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| +AutoplayPolicy::AutoplayPolicy(HTMLMediaElement* element)
|
| + : locked_pending_user_gesture_(false),
|
| + locked_pending_user_gesture_if_cross_origin_experiment_enabled_(true),
|
| + element_(element),
|
| + autoplay_visibility_observer_(nullptr),
|
| + autoplay_uma_helper_(AutoplayUmaHelper::Create(element)) {
|
| + locked_pending_user_gesture_ =
|
| + ComputeLockedPendingUserGesture(element->GetDocument());
|
| + locked_pending_user_gesture_if_cross_origin_experiment_enabled_ =
|
| + IsDocumentCrossOrigin(element->GetDocument());
|
| +}
|
| +
|
| +void AutoplayPolicy::VideoWillBeDrawnToCanvas() const {
|
| + autoplay_uma_helper_->VideoWillBeDrawnToCanvas();
|
| +}
|
| +
|
| +void AutoplayPolicy::DidMoveToNewDocument(Document& old_document) {
|
| + // If any experiment is enabled, then we want to enable a user gesture by
|
| + // default, otherwise the experiment does nothing.
|
| + bool old_document_requires_user_gesture =
|
| + ComputeLockedPendingUserGesture(old_document);
|
| + bool new_document_requires_user_gesture =
|
| + ComputeLockedPendingUserGesture(element_->GetDocument());
|
| + if (new_document_requires_user_gesture && !old_document_requires_user_gesture)
|
| + locked_pending_user_gesture_ = true;
|
| +
|
| + if (IsDocumentCrossOrigin(element_->GetDocument()) &&
|
| + !IsDocumentCrossOrigin(old_document))
|
| + locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = true;
|
| +
|
| + autoplay_uma_helper_->DidMoveToNewDocument(old_document);
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsEligibleForAutoplayMuted() const {
|
| + return element_->IsHTMLVideoElement() && element_->muted() &&
|
| + RuntimeEnabledFeatures::autoplayMutedVideosEnabled();
|
| +}
|
| +
|
| +void AutoplayPolicy::StartAutoplayMutedWhenVisible() {
|
| + // We might end up in a situation where the previous
|
| + // observer didn't had time to fire yet. We can avoid
|
| + // creating a new one in this case.
|
| + if (autoplay_visibility_observer_)
|
| + return;
|
| +
|
| + autoplay_visibility_observer_ = new ElementVisibilityObserver(
|
| + element_, WTF::Bind(&AutoplayPolicy::OnVisibilityChangedForAutoplay,
|
| + WrapWeakPersistent(this)));
|
| + autoplay_visibility_observer_->Start();
|
| +}
|
| +
|
| +void AutoplayPolicy::StopAutoplayMutedWhenVisible() {
|
| + if (!autoplay_visibility_observer_)
|
| + return;
|
| +
|
| + autoplay_visibility_observer_->Stop();
|
| + autoplay_visibility_observer_ = nullptr;
|
| +}
|
| +
|
| +bool AutoplayPolicy::RequestAutoplayUnmute() {
|
| + DCHECK(!element_->muted());
|
| + bool was_autoplaying_muted = IsAutoplayingMutedInternal(true);
|
| +
|
| + TryUnlockingUserGesture();
|
| +
|
| + if (was_autoplaying_muted) {
|
| + if (IsGestureNeededForPlayback()) {
|
| + autoplay_uma_helper_->RecordAutoplayUnmuteStatus(
|
| + AutoplayUnmuteActionStatus::kFailure);
|
| + return false;
|
| + }
|
| + autoplay_uma_helper_->RecordAutoplayUnmuteStatus(
|
| + AutoplayUnmuteActionStatus::kSuccess);
|
| + }
|
| + return true;
|
| +}
|
| +
|
| +bool AutoplayPolicy::RequestAutoplayByAttribute() {
|
| + if (!ShouldAutoplay())
|
| + return false;
|
| +
|
| + autoplay_uma_helper_->OnAutoplayInitiated(AutoplaySource::kAttribute);
|
| +
|
| + if (IsGestureNeededForPlayback()) {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kAutoplayBlocked);
|
| + return false;
|
| + }
|
| +
|
| + if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kAutoplayBlocked);
|
| + } else {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kAutoplayAllowed);
|
| + }
|
| +
|
| + // At this point the gesture is not needed for playback per the if statement
|
| + // above.
|
| + if (!IsEligibleForAutoplayMuted())
|
| + return true;
|
| +
|
| + // Autoplay muted video should be handled by AutoplayPolicy based on the
|
| + // visibily.
|
| + StartAutoplayMutedWhenVisible();
|
| + return false;
|
| +}
|
| +
|
| +Nullable<ExceptionCode> AutoplayPolicy::RequestPlay() {
|
| + if (!UserGestureIndicator::ProcessingUserGesture()) {
|
| + autoplay_uma_helper_->OnAutoplayInitiated(AutoplaySource::kMethod);
|
| + if (IsGestureNeededForPlayback()) {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kAutoplayBlocked);
|
| + return kNotAllowedError;
|
| + }
|
| +
|
| + if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kAutoplayBlocked);
|
| + } else {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kAutoplayAllowed);
|
| + }
|
| + } else {
|
| + autoplay_uma_helper_->RecordCrossOriginAutoplayResult(
|
| + CrossOriginAutoplayResult::kPlayedWithGesture);
|
| + TryUnlockingUserGesture();
|
| + }
|
| +
|
| + return nullptr;
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsAutoplayingMuted() const {
|
| + return IsAutoplayingMutedInternal(element_->muted());
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsAutoplayingMutedInternal(bool muted) const {
|
| + if (!element_->IsHTMLVideoElement() ||
|
| + !RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) {
|
| + return false;
|
| + }
|
| +
|
| + return !element_->paused() && muted && IsLockedPendingUserGesture();
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsLockedPendingUserGesture() const {
|
| + return locked_pending_user_gesture_;
|
| +}
|
| +
|
| +void AutoplayPolicy::TryUnlockingUserGesture() {
|
| + if (IsLockedPendingUserGesture() &&
|
| + UserGestureIndicator::UtilizeUserGesture()) {
|
| + UnlockUserGesture();
|
| + }
|
| +}
|
| +
|
| +void AutoplayPolicy::UnlockUserGesture() {
|
| + locked_pending_user_gesture_ = false;
|
| + locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = false;
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsGestureNeededForPlayback() const {
|
| + if (!locked_pending_user_gesture_)
|
| + return false;
|
| +
|
| + return IsGestureNeededForPlaybackIfPendingUserGestureIsLocked();
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsGestureNeededForPlaybackIfPendingUserGestureIsLocked()
|
| + const {
|
| + if (element_->GetLoadType() == WebMediaPlayer::kLoadTypeMediaStream)
|
| + return false;
|
| +
|
| + // We want to allow muted video to autoplay if:
|
| + // - the flag is enabled;
|
| + // - Data Saver is not enabled;
|
| + // - Preload was not disabled (low end devices);
|
| + // - Autoplay is enabled in settings;
|
| + if (element_->IsHTMLVideoElement() && element_->muted() &&
|
| + RuntimeEnabledFeatures::autoplayMutedVideosEnabled() &&
|
| + !(element_->GetDocument().GetSettings() &&
|
| + element_->GetDocument().GetSettings()->GetDataSaverEnabled()) &&
|
| + !(element_->GetDocument().GetSettings() &&
|
| + element_->GetDocument()
|
| + .GetSettings()
|
| + ->GetForcePreloadNoneForMediaElements()) &&
|
| + IsAutoplayAllowedPerSettings()) {
|
| + return false;
|
| + }
|
| +
|
| + return true;
|
| +}
|
| +
|
| +void AutoplayPolicy::OnVisibilityChangedForAutoplay(bool is_visible) {
|
| + if (!is_visible) {
|
| + if (element_->can_autoplay_ && element_->Autoplay()) {
|
| + element_->PauseInternal();
|
| + element_->can_autoplay_ = true;
|
| + }
|
| + return;
|
| + }
|
| +
|
| + if (ShouldAutoplay()) {
|
| + element_->paused_ = false;
|
| + element_->ScheduleEvent(EventTypeNames::play);
|
| + element_->ScheduleNotifyPlaying();
|
| +
|
| + element_->UpdatePlayState();
|
| + }
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()
|
| + const {
|
| + if (!locked_pending_user_gesture_if_cross_origin_experiment_enabled_)
|
| + return false;
|
| +
|
| + return IsGestureNeededForPlaybackIfPendingUserGestureIsLocked();
|
| +}
|
| +
|
| +bool AutoplayPolicy::IsAutoplayAllowedPerSettings() const {
|
| + LocalFrame* frame = element_->GetDocument().GetFrame();
|
| + if (!frame)
|
| + return false;
|
| + return frame->GetContentSettingsClient()->AllowAutoplay(true);
|
| +}
|
| +
|
| +bool AutoplayPolicy::ShouldAutoplay() {
|
| + if (element_->GetDocument().IsSandboxed(kSandboxAutomaticFeatures))
|
| + return false;
|
| + return element_->can_autoplay_ && element_->paused_ && element_->Autoplay();
|
| +}
|
| +
|
| +DEFINE_TRACE(AutoplayPolicy) {
|
| + visitor->Trace(element_);
|
| + visitor->Trace(autoplay_visibility_observer_);
|
| + visitor->Trace(autoplay_uma_helper_);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|