| Index: third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
|
| diff --git a/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp b/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
|
| deleted file mode 100644
|
| index 5b775af1537c55c544e762dfcf2af6125164b0f8..0000000000000000000000000000000000000000
|
| --- a/third_party/WebKit/Source/core/html/AutoplayExperimentHelper.cpp
|
| +++ /dev/null
|
| @@ -1,512 +0,0 @@
|
| -// 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 "core/html/AutoplayExperimentHelper.h"
|
| -
|
| -#include "core/dom/Document.h"
|
| -#include "core/frame/Settings.h"
|
| -#include "core/html/HTMLMediaElement.h"
|
| -#include "core/page/Page.h"
|
| -#include "platform/UserGestureIndicator.h"
|
| -#include "platform/geometry/IntRect.h"
|
| -
|
| -namespace blink {
|
| -
|
| -using namespace HTMLNames;
|
| -
|
| -// Seconds to wait after a video has stopped moving before playing it.
|
| -static const double kViewportTimerPollDelay = 0.5;
|
| -
|
| -AutoplayExperimentHelper::AutoplayExperimentHelper(Client* client)
|
| - : m_client(client),
|
| - m_mode(Mode::ExperimentOff),
|
| - m_playPending(false),
|
| - m_registeredWithLayoutObject(false),
|
| - m_wasInViewport(false),
|
| - m_autoplayMediaEncountered(false),
|
| - m_playbackStartedMetricRecorded(false),
|
| - m_waitingForAutoplayPlaybackStop(false),
|
| - m_recordedElement(false),
|
| - m_lastLocationUpdateTime(-std::numeric_limits<double>::infinity()),
|
| - m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired),
|
| - m_autoplayDeferredMetric(GesturelessPlaybackNotOverridden) {
|
| - m_mode = fromString(this->client().autoplayExperimentMode());
|
| -
|
| - DVLOG_IF(3, isExperimentEnabled()) << "autoplay experiment set to " << m_mode;
|
| -}
|
| -
|
| -AutoplayExperimentHelper::~AutoplayExperimentHelper() {}
|
| -
|
| -void AutoplayExperimentHelper::becameReadyToPlay() {
|
| - // Assuming that we're eligible to override the user gesture requirement,
|
| - // either play if we meet the visibility checks, or install a listener
|
| - // to wait for them to pass. We do not actually start playback; our
|
| - // caller must do that.
|
| - autoplayMediaEncountered();
|
| -
|
| - if (isEligible()) {
|
| - if (meetsVisibilityRequirements())
|
| - prepareToAutoplay(GesturelessPlaybackStartedByAutoplayFlagImmediately);
|
| - else
|
| - registerForPositionUpdatesIfNeeded();
|
| - }
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::playMethodCalled() {
|
| - // If a play is already pending, then do nothing. We're already trying
|
| - // to play. Similarly, do nothing if we're already playing.
|
| - if (m_playPending || !m_client->paused())
|
| - return;
|
| -
|
| - if (!UserGestureIndicator::utilizeUserGesture()) {
|
| - autoplayMediaEncountered();
|
| -
|
| - // Check for eligibility, but don't worry if playback is currently
|
| - // pending. If we're still not eligible, then this play() will fail.
|
| - if (isEligible(IgnorePendingPlayback)) {
|
| - m_playPending = true;
|
| -
|
| - // If we are able to override the gesture requirement now, then
|
| - // do so. Otherwise, install an event listener if we need one.
|
| - // We do not actually start playback; play() will do that.
|
| - if (meetsVisibilityRequirements()) {
|
| - // Override the gesture and assume that play() will succeed.
|
| - prepareToAutoplay(GesturelessPlaybackStartedByPlayMethodImmediately);
|
| - } else {
|
| - // Wait for viewport visibility.
|
| - // TODO(liberato): if the autoplay is allowed soon enough, then
|
| - // it should still record *Immediately. Otherwise, we end up
|
| - // here before the first layout sometimes, when the item is
|
| - // visible but we just don't know that yet.
|
| - registerForPositionUpdatesIfNeeded();
|
| - }
|
| - }
|
| - } else if (isLockedPendingUserGesture()) {
|
| - // If this media tried to autoplay, and we haven't played it yet, then
|
| - // record that the user provided the gesture to start it the first time.
|
| - if (m_autoplayMediaEncountered && !m_playbackStartedMetricRecorded)
|
| - recordAutoplayMetric(AutoplayManualStart);
|
| - // Don't let future gestureless playbacks affect metrics.
|
| - m_autoplayMediaEncountered = true;
|
| - m_playbackStartedMetricRecorded = true;
|
| - m_playPending = false;
|
| -
|
| - unregisterForPositionUpdatesIfNeeded();
|
| - }
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::pauseMethodCalled() {
|
| - // Don't try to autoplay, if we would have.
|
| - m_playPending = false;
|
| - unregisterForPositionUpdatesIfNeeded();
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::loadMethodCalled() {
|
| - if (isLockedPendingUserGesture() &&
|
| - UserGestureIndicator::utilizeUserGesture()) {
|
| - recordAutoplayMetric(AutoplayEnabledThroughLoad);
|
| - unlockUserGesture(GesturelessPlaybackEnabledByLoad);
|
| - }
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::mutedChanged() {
|
| - // Mute changes are always allowed if this is unlocked.
|
| - if (!client().isLockedPendingUserGesture())
|
| - return;
|
| -
|
| - // Changes with a user gesture are okay.
|
| - if (UserGestureIndicator::utilizeUserGesture())
|
| - return;
|
| -
|
| - // If the mute state has changed to 'muted', then it's okay.
|
| - if (client().muted())
|
| - return;
|
| -
|
| - // If nothing is playing, then changes are okay too.
|
| - if (client().paused())
|
| - return;
|
| -
|
| - // Trying to unmute without a user gesture.
|
| -
|
| - // If we don't care about muted state, then it's okay.
|
| - if (!enabled(IfMuted) && !(client().isCrossOrigin() && enabled(OrMuted)))
|
| - return;
|
| -
|
| - // Unmuting isn't allowed, so pause.
|
| - client().pauseInternal();
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded() {
|
| - // If we don't require that the player is in the viewport, then we don't
|
| - // need the listener.
|
| - if (!requiresViewportVisibility()) {
|
| - if (!enabled(IfPageVisible))
|
| - return;
|
| - }
|
| -
|
| - m_client->setRequestPositionUpdates(true);
|
| -
|
| - // Set this unconditionally, in case we have no layout object yet.
|
| - m_registeredWithLayoutObject = true;
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded() {
|
| - if (m_registeredWithLayoutObject)
|
| - m_client->setRequestPositionUpdates(false);
|
| -
|
| - // Clear this unconditionally so that we don't re-register if we didn't
|
| - // have a LayoutObject now, but get one later.
|
| - m_registeredWithLayoutObject = false;
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::positionChanged(const IntRect& visibleRect) {
|
| - // Something, maybe position, has changed. If applicable, start a
|
| - // timer to look for the end of a scroll operation.
|
| - // Don't do much work here.
|
| - // Also note that we are called quite often, including when the
|
| - // page becomes visible. That's why we don't bother to register
|
| - // for page visibility changes explicitly.
|
| - if (visibleRect.isEmpty())
|
| - return;
|
| -
|
| - m_lastVisibleRect = visibleRect;
|
| -
|
| - IntRect currentLocation = client().absoluteBoundingBoxRect();
|
| - if (currentLocation.isEmpty())
|
| - return;
|
| -
|
| - bool inViewport = meetsVisibilityRequirements();
|
| -
|
| - if (m_lastLocation != currentLocation) {
|
| - m_lastLocationUpdateTime = monotonicallyIncreasingTime();
|
| - m_lastLocation = currentLocation;
|
| - }
|
| -
|
| - if (inViewport && !m_wasInViewport) {
|
| - // Only reset the timer when we transition from not visible to
|
| - // visible, because resetting the timer isn't cheap.
|
| - m_viewportTimer.startOneShot(kViewportTimerPollDelay, BLINK_FROM_HERE);
|
| - }
|
| - m_wasInViewport = inViewport;
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::updatePositionNotificationRegistration() {
|
| - if (m_registeredWithLayoutObject)
|
| - m_client->setRequestPositionUpdates(true);
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::triggerAutoplayViewportCheckForTesting() {
|
| - // Make sure that the last update appears to be sufficiently far in the
|
| - // past to appear that scrolling has stopped by now in viewportTimerFired.
|
| - m_lastLocationUpdateTime =
|
| - monotonicallyIncreasingTime() - kViewportTimerPollDelay - 1;
|
| - viewportTimerFired(nullptr);
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::viewportTimerFired(TimerBase*) {
|
| - double now = monotonicallyIncreasingTime();
|
| - double delta = now - m_lastLocationUpdateTime;
|
| - if (delta < kViewportTimerPollDelay) {
|
| - // If we are not visible, then skip the timer. It will be started
|
| - // again if we become visible again.
|
| - if (m_wasInViewport)
|
| - m_viewportTimer.startOneShot(kViewportTimerPollDelay - delta,
|
| - BLINK_FROM_HERE);
|
| -
|
| - return;
|
| - }
|
| -
|
| - // Sufficient time has passed since the last scroll that we'll
|
| - // treat it as the end of scroll. Autoplay if we should.
|
| - maybeStartPlaying();
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::meetsVisibilityRequirements() const {
|
| - if (enabled(IfPageVisible) &&
|
| - client().pageVisibilityState() != PageVisibilityStateVisible)
|
| - return false;
|
| -
|
| - if (!requiresViewportVisibility())
|
| - return true;
|
| -
|
| - if (m_lastVisibleRect.isEmpty())
|
| - return false;
|
| -
|
| - IntRect currentLocation = client().absoluteBoundingBoxRect();
|
| - if (currentLocation.isEmpty())
|
| - return false;
|
| -
|
| - // In partial-viewport mode, we require only 1x1 area.
|
| - if (enabled(IfPartialViewport)) {
|
| - return m_lastVisibleRect.intersects(currentLocation);
|
| - }
|
| -
|
| - // Element must be completely visible, or as much as fits.
|
| - // If element completely fills the screen, then truncate it to exactly
|
| - // match the screen. Any element that is wider just has to cover.
|
| - if (currentLocation.x() <= m_lastVisibleRect.x() &&
|
| - currentLocation.x() + currentLocation.width() >=
|
| - m_lastVisibleRect.x() + m_lastVisibleRect.width()) {
|
| - currentLocation.setX(m_lastVisibleRect.x());
|
| - currentLocation.setWidth(m_lastVisibleRect.width());
|
| - }
|
| -
|
| - if (currentLocation.y() <= m_lastVisibleRect.y() &&
|
| - currentLocation.y() + currentLocation.height() >=
|
| - m_lastVisibleRect.y() + m_lastVisibleRect.height()) {
|
| - currentLocation.setY(m_lastVisibleRect.y());
|
| - currentLocation.setHeight(m_lastVisibleRect.height());
|
| - }
|
| -
|
| - return m_lastVisibleRect.contains(currentLocation);
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::maybeStartPlaying() {
|
| - // See if we're allowed to autoplay now.
|
| - if (!isGestureRequirementOverridden())
|
| - return false;
|
| -
|
| - // Start playing!
|
| - prepareToAutoplay(client().shouldAutoplay()
|
| - ? GesturelessPlaybackStartedByAutoplayFlagAfterScroll
|
| - : GesturelessPlaybackStartedByPlayMethodAfterScroll);
|
| -
|
| - // Record that this played without a user gesture.
|
| - // This should rarely actually do anything. Usually, playMethodCalled()
|
| - // and becameReadyToPlay will handle it, but toggling muted state can,
|
| - // in some cases, also trigger autoplay if the autoplay attribute is set
|
| - // after the media is ready to play.
|
| - autoplayMediaEncountered();
|
| -
|
| - client().playInternal();
|
| -
|
| - return true;
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::isGestureRequirementOverridden() const {
|
| - return isEligible() && meetsVisibilityRequirements();
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::isPlaybackDeferred() const {
|
| - return m_playPending;
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::isEligible(EligibilityMode mode) const {
|
| - if (m_mode == Mode::ExperimentOff)
|
| - return false;
|
| -
|
| - // If autoplay is disabled, no one is eligible.
|
| - if (!client().isAutoplayAllowedPerSettings())
|
| - return false;
|
| -
|
| - // If no user gesture is required, then the experiment doesn't apply.
|
| - // This is what prevents us from starting playback more than once.
|
| - // Since this flag is never set to true once it's cleared, it will block
|
| - // the autoplay experiment forever.
|
| - if (!isLockedPendingUserGesture())
|
| - return false;
|
| -
|
| - // Make sure that this is an element of the right type.
|
| - if (!enabled(ForVideo) && client().isHTMLVideoElement())
|
| - return false;
|
| -
|
| - if (!enabled(ForAudio) && client().isHTMLAudioElement())
|
| - return false;
|
| -
|
| - // If nobody has requested playback, either by the autoplay attribute or
|
| - // a play() call, then do nothing.
|
| -
|
| - if (mode != IgnorePendingPlayback && !m_playPending &&
|
| - !client().shouldAutoplay())
|
| - return false;
|
| -
|
| - // Note that the viewport test always returns false on desktop, which is
|
| - // why video-autoplay-experiment.html doesn't check -ifmobile .
|
| - if (enabled(IfMobile) && !client().isLegacyViewportType())
|
| - return false;
|
| -
|
| - // If we require same-origin, then check the origin.
|
| - if (enabled(IfSameOrigin) && client().isCrossOrigin()) {
|
| - // We're cross-origin, so block unless it's muted content and OrMuted
|
| - // is enabled. For good measure, we also block all audio elements.
|
| - if (client().isHTMLAudioElement() || !client().muted() ||
|
| - !enabled(OrMuted)) {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - // If we require muted media and this is muted, then it is eligible.
|
| - if (enabled(IfMuted))
|
| - return client().muted();
|
| -
|
| - // Element is eligible for gesture override, maybe muted.
|
| - return true;
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::muteIfNeeded() {
|
| - if (enabled(PlayMuted))
|
| - client().setMuted(true);
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::unlockUserGesture(AutoplayMetrics metric) {
|
| - // Note that this could be moved back into HTMLMediaElement fairly easily.
|
| - // It's only here so that we can record the reason, and we can hide the
|
| - // ordering between unlocking and recording from the element this way.
|
| - if (!client().isLockedPendingUserGesture())
|
| - return;
|
| -
|
| - setDeferredOverrideReason(metric);
|
| - client().unlockUserGesture();
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::setDeferredOverrideReason(
|
| - AutoplayMetrics metric) {
|
| - // If the player is unlocked, then we don't care about any later reason.
|
| - if (!client().isLockedPendingUserGesture())
|
| - return;
|
| -
|
| - m_autoplayDeferredMetric = metric;
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::prepareToAutoplay(AutoplayMetrics metric) {
|
| - // This also causes !isEligible, so that we don't allow autoplay more than
|
| - // once. Be sure to do this before muteIfNeeded().
|
| - // Also note that, at this point, we know that we're goint to start
|
| - // playback. However, we still don't record the metric here. Instead,
|
| - // we let playbackStarted() do that later.
|
| - setDeferredOverrideReason(metric);
|
| -
|
| - // Don't bother to call autoplayMediaEncountered, since whoever initiates
|
| - // playback has do it anyway, in case we don't allow autoplay.
|
| -
|
| - unregisterForPositionUpdatesIfNeeded();
|
| - muteIfNeeded();
|
| -
|
| - // Do not actually start playback here.
|
| -}
|
| -
|
| -AutoplayExperimentHelper::Mode AutoplayExperimentHelper::fromString(
|
| - const String& mode) {
|
| - Mode value = ExperimentOff;
|
| - if (mode.contains("-forvideo"))
|
| - value |= ForVideo;
|
| - if (mode.contains("-foraudio"))
|
| - value |= ForAudio;
|
| - if (mode.contains("-ifpagevisible"))
|
| - value |= IfPageVisible;
|
| - if (mode.contains("-ifviewport"))
|
| - value |= IfViewport;
|
| - if (mode.contains("-ifpartialviewport"))
|
| - value |= IfPartialViewport;
|
| - if (mode.contains("-ifmuted"))
|
| - value |= IfMuted;
|
| - if (mode.contains("-ifmobile"))
|
| - value |= IfMobile;
|
| - if (mode.contains("-ifsameorigin"))
|
| - value |= IfSameOrigin;
|
| - if (mode.contains("-ormuted"))
|
| - value |= OrMuted;
|
| - if (mode.contains("-playmuted"))
|
| - value |= PlayMuted;
|
| -
|
| - return value;
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::autoplayMediaEncountered() {
|
| - if (!m_autoplayMediaEncountered) {
|
| - m_autoplayMediaEncountered = true;
|
| - recordAutoplayMetric(AutoplayMediaFound);
|
| - }
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::isLockedPendingUserGesture() const {
|
| - return client().isLockedPendingUserGesture();
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::playbackStarted() {
|
| - recordAutoplayMetric(AnyPlaybackStarted);
|
| -
|
| - // Forget about our most recent visibility check. If another override is
|
| - // requested, then we'll have to refresh it. That way, we don't need to
|
| - // keep it up to date in the interim.
|
| - m_lastVisibleRect = IntRect();
|
| - m_wasInViewport = false;
|
| -
|
| - // Any pending play is now playing.
|
| - m_playPending = false;
|
| -
|
| - if (m_playbackStartedMetricRecorded)
|
| - return;
|
| -
|
| - // Whether we record anything or not, we only want to record metrics for
|
| - // the initial playback.
|
| - m_playbackStartedMetricRecorded = true;
|
| -
|
| - // If this is a gestureless start, then record why it was allowed.
|
| - if (m_autoplayMediaEncountered) {
|
| - m_waitingForAutoplayPlaybackStop = true;
|
| - recordAutoplayMetric(m_autoplayDeferredMetric);
|
| - }
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::playbackStopped() {
|
| - const bool ended = client().ended();
|
| - const bool bailout = isBailout();
|
| -
|
| - // Record that play was paused. We don't care if it was autoplay,
|
| - // play(), or the user manually started it.
|
| - recordAutoplayMetric(ended ? AnyPlaybackComplete : AnyPlaybackPaused);
|
| - if (bailout)
|
| - recordAutoplayMetric(AnyPlaybackBailout);
|
| -
|
| - // If this was a gestureless play, then record that separately.
|
| - // These cover attr and play() gestureless starts.
|
| - if (m_waitingForAutoplayPlaybackStop) {
|
| - m_waitingForAutoplayPlaybackStop = false;
|
| -
|
| - recordAutoplayMetric(ended ? AutoplayComplete : AutoplayPaused);
|
| -
|
| - if (bailout)
|
| - recordAutoplayMetric(AutoplayBailout);
|
| - }
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::recordAutoplayMetric(AutoplayMetrics metric) {
|
| - client().recordAutoplayMetric(metric);
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::isBailout() const {
|
| - // We count the user as having bailed-out on the video if they watched
|
| - // less than one minute and less than 50% of it.
|
| - const double playedTime = client().currentTime();
|
| - const double progress = playedTime / client().duration();
|
| - return (playedTime < 60) && (progress < 0.5);
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::recordSandboxFailure() {
|
| - // We record autoplayMediaEncountered here because we know
|
| - // that the autoplay attempt will fail.
|
| - autoplayMediaEncountered();
|
| - recordAutoplayMetric(AutoplayDisabledBySandbox);
|
| -}
|
| -
|
| -void AutoplayExperimentHelper::loadingStarted() {
|
| - if (m_recordedElement)
|
| - return;
|
| -
|
| - m_recordedElement = true;
|
| - recordAutoplayMetric(client().isHTMLVideoElement() ? AnyVideoElement
|
| - : AnyAudioElement);
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::requiresViewportVisibility() const {
|
| - return client().isHTMLVideoElement() &&
|
| - (enabled(IfViewport) || enabled(IfPartialViewport));
|
| -}
|
| -
|
| -bool AutoplayExperimentHelper::isExperimentEnabled() {
|
| - return m_mode != Mode::ExperimentOff;
|
| -}
|
| -
|
| -} // namespace blink
|
|
|