blob: aa396235ac507f7900034911b7d8c386df84d01a [file] [log] [blame]
// Copyright 2015 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.
#ifndef AutoplayExperimentHelper_h
#define AutoplayExperimentHelper_h
#include "core/page/PageVisibilityState.h"
#include "platform/Timer.h"
#include "platform/geometry/IntRect.h"
namespace blink {
class Document;
class AutoplayExperimentTest;
// These values are used for a histogram. Do not reorder.
enum AutoplayMetrics {
// Media element with autoplay seen.
AutoplayMediaFound = 0,
// Autoplay enabled and user stopped media play at any point.
AutoplayPaused = 1,
// Autoplay enabled but user bailed out on media play early.
AutoplayBailout = 2,
// Autoplay disabled but user manually started media.
AutoplayManualStart = 3,
// Autoplay was (re)enabled through a user-gesture triggered load()
AutoplayEnabledThroughLoad = 4,
// Autoplay disabled by sandbox flags.
AutoplayDisabledBySandbox = 5,
// These metrics indicate "no gesture detected, but the gesture
// requirement was overridden by experiment". They do not include cases
// where no user gesture is required (!m_userGestureRequiredForPlay).
// User gesture requirement was bypassed, and playback started, during
// initial load via the autoplay flag. If playback was deferred due to
// visibility requirements, then this does not apply.
GesturelessPlaybackStartedByAutoplayFlagImmediately = 6,
// User gesture requirement was bypassed, and playback started, when
// the play() method was called. If playback is deferred due to
// visibility requirements, then this does not apply.
GesturelessPlaybackStartedByPlayMethodImmediately = 7,
// User gesture requirement was bypassed, and playback started, after
// an element with the autoplay flag moved into the viewport. Playback
// was deferred earlier due to visibility requirements.
GesturelessPlaybackStartedByAutoplayFlagAfterScroll = 8,
// User gesture requirement was bypassed, and playback started, after
// an element on which the play() method was called was moved into the
// viewport. Playback had been deferred due to visibility requirements.
GesturelessPlaybackStartedByPlayMethodAfterScroll = 9,
// play() failed to play due to gesture requirement.
PlayMethodFailed = 10,
// Some play, whether user initiated or not, started.
AnyPlaybackStarted = 11,
// Some play, whether user initiated or not, paused.
AnyPlaybackPaused = 12,
// Some playback, whether user initiated or not, bailed out early.
AnyPlaybackBailout = 13,
// Some playback, whether user initiated or not, played to completion.
AnyPlaybackComplete = 14,
// Number of audio elements detected that reach the resource fetch algorithm.
AnyAudioElement = 15,
// Numer of video elements detected that reach the resource fetch algorithm.
AnyVideoElement = 16,
// User gesture was bypassed, and playback started, and media played to
// completion without a user-initiated pause.
AutoplayComplete = 17,
// Autoplay started after the gesture requirement was removed by a
// user gesture load().
GesturelessPlaybackEnabledByLoad = 18,
// Gestureless playback started after the gesture requirement was removed
// because src is media stream.
GesturelessPlaybackEnabledByStream = 19,
// Gestureless playback was started, but was never overridden. This
// includes the case in which no gesture was ever required.
GesturelessPlaybackNotOverridden = 20,
// Gestureless playback was enabled by a user gesture play() call.
GesturelessPlaybackEnabledByPlayMethod = 21,
// This enum value must be last.
NumberOfAutoplayMetrics,
};
class CORE_EXPORT AutoplayExperimentHelper final :
public GarbageCollectedFinalized<AutoplayExperimentHelper> {
friend class AutoplayExperimentTest;
public:
// For easier testing, collect all the things we care about here.
class Client : public GarbageCollectedFinalized<Client> {
public:
virtual ~Client() {}
// HTMLMediaElement
virtual double currentTime() const = 0;
virtual double duration() const = 0;
virtual bool paused() const = 0;
virtual bool ended() const = 0;
virtual bool muted() const = 0;
virtual void setMuted(bool) = 0;
virtual void playInternal() = 0;
virtual void pauseInternal() = 0;
virtual bool isLockedPendingUserGesture() const = 0;
virtual void unlockUserGesture() = 0;
virtual void recordAutoplayMetric(AutoplayMetrics) = 0;
virtual bool shouldAutoplay() = 0;
virtual bool isHTMLVideoElement() const = 0;
virtual bool isHTMLAudioElement() const = 0;
// Document
virtual bool isLegacyViewportType() = 0;
virtual PageVisibilityState pageVisibilityState() const = 0;
virtual String autoplayExperimentMode() const = 0;
// Frame
virtual bool isCrossOrigin() const = 0;
virtual bool isAutoplayAllowedPerSettings() const = 0;
// LayoutObject
virtual void setRequestPositionUpdates(bool) = 0;
virtual IntRect absoluteBoundingBoxRect() const = 0;
DEFINE_INLINE_VIRTUAL_TRACE() { }
};
static AutoplayExperimentHelper* create(Client* client)
{
return new AutoplayExperimentHelper(client);
}
~AutoplayExperimentHelper();
void becameReadyToPlay();
void playMethodCalled();
void pauseMethodCalled();
void loadMethodCalled();
void mutedChanged();
void positionChanged(const IntRect&);
void updatePositionNotificationRegistration();
void recordSandboxFailure();
void loadingStarted();
void playbackStarted();
void playbackStopped();
void initialPlayWithUserGesture();
// Returns true if and only if any experiment is enabled (i.e., |m_mode|
// is not ExperimentOff).
bool isExperimentEnabled();
// Remove the user gesture requirement, and record why. If there is no
// gesture requirement, then this does nothing.
void unlockUserGesture(AutoplayMetrics);
// Set the reason that we're overridding the user gesture. If there is no
// gesture requirement, then this does nothing.
void setDeferredOverrideReason(AutoplayMetrics);
// Return true if and only if the user gesture requirement is currently
// overridden by the experiment, permitting playback.
bool isGestureRequirementOverridden() const;
// Return true if and only if playback is queued but hasn't started yet,
// such as if the element doesn't meet visibility requirements.
bool isPlaybackDeferred() const;
// Set the position to the current view's position, and
void triggerAutoplayViewportCheckForTesting();
enum Mode {
// Do not enable the autoplay experiment.
ExperimentOff = 0,
// Enable gestureless autoplay for video elements.
ForVideo = 1 << 0,
// Enable gestureless autoplay for audio elements.
ForAudio = 1 << 1,
// Restrict gestureless autoplay to media that is in a visible page.
IfPageVisible = 1 << 2,
// Restrict gestureless autoplay to media that is entirely visible in
// the viewport.
IfViewport = 1 << 3,
// Restrict gestureless autoplay to media that is partially visible in
// the viewport.
IfPartialViewport = 1 << 4,
// Restrict gestureless autoplay to audio-less or muted media.
IfMuted = 1 << 5,
// Restrict gestureless autoplay to sites which contain the
// viewport tag.
IfMobile = 1 << 6,
// Restrict gestureless autoplay to sites which are from the same origin
// as the top-level frame.
IfSameOrigin = 1 << 7,
// Extend IfSameOrigin to allow autoplay of cross-origin elements if
// they're muted. This has no effect on same-origin or if IfSameOrigin
// isn't enabled.
OrMuted = 1 << 8,
// If gestureless autoplay is allowed, then mute the media before
// starting to play.
PlayMuted = 1 << 9,
};
DEFINE_INLINE_TRACE() { visitor->trace(m_client); }
private:
explicit AutoplayExperimentHelper(Client*);
// Register to receive position updates, if we haven't already. If we
// have, then this does nothing.
void registerForPositionUpdatesIfNeeded();
// Un-register for position updates, if we are currently registered.
void unregisterForPositionUpdatesIfNeeded();
// Modifiers for checking isEligible().
enum EligibilityMode {
// Perform all normal eligibility checks.
Normal = 0,
// Perform normal eligibility checks, but skip checking if autoplay has
// actually been requested. In other words, don't fail just becase
// nobody has called play() and/or set the autoplay attribute.
IgnorePendingPlayback = 1
};
// Return true if any only if this player meets (most) of the eligibility
// requirements for the experiment to override the need for a user
// gesture. This includes everything except the visibility test.
// |mode| modifies the eligibility check, as described above.
bool isEligible(EligibilityMode = Normal) const;
// Return false if and only if m_element is not visible, and we care
// that it must be visible.
bool meetsVisibilityRequirements() const;
// Set the muted flag on the media if we're in an experiment mode that
// requires it, else do nothing.
void muteIfNeeded();
// Maybe override the requirement for a user gesture, and start playing
// autoplay media. Returns true if only if it starts playback.
bool maybeStartPlaying();
// Configure internal state to record that the autoplay experiment is
// going to start playback. This doesn't actually start playback, since
// there are several different cases.
void prepareToAutoplay(AutoplayMetrics);
// Record that an attempt to play without a user gesture has happened.
// This does not assume whether or not the play attempt will succeed.
// This method takes no action after it is called once.
void autoplayMediaEncountered();
// If we are about to enter a paused state, call this to record
// autoplay metrics.
void recordMetricsBeforePause();
// Process a timer for checking visibility.
void viewportTimerFired(Timer<AutoplayExperimentHelper>*);
Client& client() const { return *m_client; }
bool isLockedPendingUserGesture() const;
inline bool enabled(Mode mode) const
{
return static_cast<int>(m_mode) & static_cast<int>(mode);
}
Mode fromString(const String& mode);
void recordAutoplayMetric(AutoplayMetrics);
// Could stopping at this point be considered a bailout of playback?
// (as in, "The user really didn't want to play this").
bool isBailout() const;
// Returns true if and only if the experiment requires some sort of viewport
// visibility check for autoplay.
bool requiresViewportVisibility() const;
Member<Client> m_client;
Mode m_mode;
// Autoplay experiment state.
// True if we've received a play() without a pause().
bool m_playPending : 1;
// Are we registered with the view for position updates?
bool m_registeredWithLayoutObject : 1;
// According to our last position update, are we in the viewport?
bool m_wasInViewport : 1;
// Have we counted this autoplay media in the metrics yet? This is set when
// a media element tries to autoplay, and we record that via the
// AutoplayMediaFound metric.
bool m_autoplayMediaEncountered : 1;
// Have we recorded a metric about the cause of the initial playback of
// this media yet?
bool m_playbackStartedMetricRecorded : 1;
// Is the current playback the result of autoplay? If so, then this flag
// records that the pause / stop should be counted in the autoplay metrics.
bool m_waitingForAutoplayPlaybackStop : 1;
// Did we record that this media element exists in the metrics yet? This is
// independent of whether it autoplays; we just want to know how many
// elements exist for the Any{Audio|Video}Element metrics.
bool m_recordedElement : 1;
// According to our last position update, where was our element?
IntRect m_lastLocation;
IntRect m_lastVisibleRect;
// When was m_lastLocation set?
double m_lastLocationUpdateTime;
Timer<AutoplayExperimentHelper> m_viewportTimer;
// Reason that autoplay would be allowed to proceed without a user gesture.
AutoplayMetrics m_autoplayDeferredMetric;
};
inline AutoplayExperimentHelper::Mode& operator|=(AutoplayExperimentHelper::Mode& a,
const AutoplayExperimentHelper::Mode& b)
{
a = static_cast<AutoplayExperimentHelper::Mode>(static_cast<int>(a) | static_cast<int>(b));
return a;
}
} // namespace blink
#endif // AutoplayExperimentHelper_h