Chromium Code Reviews| 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..286a173c391736ff1c6362ae83054978ed8ad93c |
| --- /dev/null |
| +++ b/third_party/WebKit/Source/core/html/media/AutoplayPolicy.cpp |
| @@ -0,0 +1,254 @@ |
| +// 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(); |
| +} |
| + |
| +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) { |
| + locked_pending_user_gesture_ = |
| + ComputeLockedPendingUserGesture(element->GetDocument()); |
| + locked_pending_user_gesture_if_cross_origin_experiment_enabled_ = |
| + IsDocumentCrossOrigin(element->GetDocument()); |
| +} |
| + |
| +AutoplayPolicy::~AutoplayPolicy() = default; |
| + |
| +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; |
| +} |
| + |
| +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_) { |
|
mlamouri (slow - plz ping)
2017/04/18 13:28:28
Early return?
Zhiqiang Zhang (Slow)
2017/04/18 16:56:44
Done.
|
| + autoplay_visibility_observer_ = new ElementVisibilityObserver( |
| + element_, WTF::Bind(&AutoplayPolicy::OnVisibilityChangedForAutoplay, |
| + WrapWeakPersistent(this))); |
| + autoplay_visibility_observer_->Start(); |
| + } |
| +} |
| + |
| +void AutoplayPolicy::StopAutoplayMutedWhenVisible() { |
| + if (autoplay_visibility_observer_) { |
|
mlamouri (slow - plz ping)
2017/04/18 13:28:28
ditto
Zhiqiang Zhang (Slow)
2017/04/18 16:56:44
Done.
|
| + autoplay_visibility_observer_->Stop(); |
| + autoplay_visibility_observer_ = nullptr; |
| + } |
| +} |
| + |
| +bool AutoplayPolicy::CheckUnmuteShouldPauseAutoplay() { |
| + if (!element_->IsHTMLVideoElement() || |
| + !RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) { |
| + return false; |
| + } |
| + bool result = |
| + !element_->paused() && element_->muted() && IsLockedPendingUserGesture(); |
| + if (result) { |
| + element_->autoplay_uma_helper().RecordAutoplayUnmuteStatus( |
| + AutoplayUnmuteActionStatus::kFailure); |
| + } else { |
| + element_->autoplay_uma_helper().RecordAutoplayUnmuteStatus( |
| + AutoplayUnmuteActionStatus::kSuccess); |
| + } |
| + return result; |
| +} |
| + |
| +Nullable<ExceptionCode> AutoplayPolicy::CheckPlayMethodAllowed() { |
| + if (!UserGestureIndicator::ProcessingUserGesture()) { |
| + element_->autoplay_uma_helper().OnAutoplayInitiated( |
| + AutoplaySource::kMethod); |
| + if (IsGestureNeededForPlayback()) { |
| + element_->autoplay_uma_helper().RecordCrossOriginAutoplayResult( |
| + CrossOriginAutoplayResult::kAutoplayBlocked); |
| + return kNotAllowedError; |
| + } |
| + |
| + if (IsGestureNeededForPlaybackIfCrossOriginExperimentEnabled()) { |
| + element_->autoplay_uma_helper().RecordCrossOriginAutoplayResult( |
| + CrossOriginAutoplayResult::kAutoplayBlocked); |
| + } else { |
| + element_->autoplay_uma_helper().RecordCrossOriginAutoplayResult( |
| + CrossOriginAutoplayResult::kAutoplayAllowed); |
| + } |
| + } else { |
| + element_->autoplay_uma_helper().RecordCrossOriginAutoplayResult( |
| + CrossOriginAutoplayResult::kPlayedWithGesture); |
| + UserGestureIndicator::UtilizeUserGesture(); |
| + UnlockUserGesture(); |
| + } |
| + return nullptr; |
| +} |
| + |
| +bool AutoplayPolicy::IsAutoplayingMuted() { |
| + if (!element_->IsHTMLVideoElement() || |
| + !RuntimeEnabledFeatures::autoplayMutedVideosEnabled()) { |
| + return false; |
| + } |
| + |
| + return !element_->paused() && element_->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 (element_->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 HTMLMediaElement::IsInCrossOriginFrame() const { |
| + return IsDocumentCrossOrigin(GetDocument()); |
| +} |
| + |
| +DEFINE_TRACE(AutoplayPolicy) { |
| + visitor->Trace(element_); |
| + visitor->Trace(autoplay_visibility_observer_); |
| +} |
| + |
| +} // namespace blink |