| Index: Source/core/html/AutoplayExperimentHelper.cpp | 
| diff --git a/Source/core/html/AutoplayExperimentHelper.cpp b/Source/core/html/AutoplayExperimentHelper.cpp | 
| index 643eaceb0bda153f928cddc09d7c0cb8cba3cb46..727d8c118e43557c3b391ae216368c8c59d39188 100644 | 
| --- a/Source/core/html/AutoplayExperimentHelper.cpp | 
| +++ b/Source/core/html/AutoplayExperimentHelper.cpp | 
| @@ -21,10 +21,17 @@ namespace blink { | 
|  | 
| using namespace HTMLNames; | 
|  | 
| +// How long do we wait after a scroll event before deciding that no more | 
| +// scroll events are going to arrive? | 
| +static const double viewportTimerPollDelay = 0.5; | 
| + | 
| AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) | 
| : m_element(element) | 
| , m_mode(AutoplayExperimentConfig::Mode::Off) | 
| , m_playPending(false) | 
| +    , m_registeredWithView(false) | 
| +    , m_wasInViewport(false) | 
| +    , m_viewportTimer(this, &AutoplayExperimentHelper::viewportTimerFired) | 
| { | 
| if (document().settings()) { | 
| m_mode = AutoplayExperimentConfig::fromString(document().settings()->autoplayExperimentMode()); | 
| @@ -38,14 +45,19 @@ AutoplayExperimentHelper::AutoplayExperimentHelper(HTMLMediaElement& element) | 
|  | 
| AutoplayExperimentHelper::~AutoplayExperimentHelper() | 
| { | 
| +    unregisterForPositionUpdatesIfNeeded(); | 
| } | 
|  | 
| void AutoplayExperimentHelper::becameReadyToPlay() | 
| { | 
| // Assuming that we're eligible to override the user gesture requirement, | 
| -    // then play. | 
| +    // either play if we meet the visibility checks, or install a listener | 
| +    // to wait for them to pass. | 
| if (isEligible()) { | 
| -        prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); | 
| +        if (isInViewportIfNeeded()) | 
| +            prepareToPlay(GesturelessPlaybackStartedByAutoplayFlagImmediately); | 
| +        else | 
| +            registerForPositionUpdatesIfNeeded(); | 
| } | 
| } | 
|  | 
| @@ -61,11 +73,19 @@ void AutoplayExperimentHelper::playMethodCalled() | 
| if (isEligible()) { | 
| // Remember that userGestureRequiredForPlay is required for | 
| // us to be eligible for the experiment. | 
| -            // We are able to override the gesture requirement now, so | 
| -            // do so. | 
| -            prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately); | 
| +            // If we are able to override the gesture requirement now, then | 
| +            // do so.  Otherwise, install an event listener if we need one. | 
| +            if (isInViewportIfNeeded()) { | 
| +                // Override the gesture and play. | 
| +                prepareToPlay(GesturelessPlaybackStartedByPlayMethodImmediately); | 
| +            } else { | 
| +                // Wait for viewport visibility. | 
| +                registerForPositionUpdatesIfNeeded(); | 
| +            } | 
| } | 
|  | 
| +    } else if (m_element.isUserGestureRequiredForPlay()) { | 
| +        unregisterForPositionUpdatesIfNeeded(); | 
| } | 
| } | 
|  | 
| @@ -73,18 +93,101 @@ void AutoplayExperimentHelper::pauseMethodCalled() | 
| { | 
| // Don't try to autoplay, if we would have. | 
| m_playPending = false; | 
| +    unregisterForPositionUpdatesIfNeeded(); | 
| } | 
|  | 
| void AutoplayExperimentHelper::mutedChanged() | 
| { | 
| -    // In other words, start playing if we just needed 'mute' to autoplay. | 
| +    // If we are no longer eligible for the autoplay experiment, then also | 
| +    // quit listening for events.  If we are eligible, and if we should be | 
| +    // playing, then start playing.  In other words, start playing if | 
| +    // we just needed 'mute' to autoplay. | 
| +    if (!isEligible()) { | 
| +        unregisterForPositionUpdatesIfNeeded(); | 
| +    } else { | 
| +        // Try to play.  If we can't, then install a listener. | 
| +        if (!maybeStartPlaying()) | 
| +            registerForPositionUpdatesIfNeeded(); | 
| +    } | 
| +} | 
| + | 
| +void AutoplayExperimentHelper::registerForPositionUpdatesIfNeeded() | 
| +{ | 
| +    // If we don't require that the player is in the viewport, then we don't | 
| +    // need the listener. | 
| +    if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)) | 
| +        return; | 
| + | 
| +    LayoutObject* layoutObject = m_element.layoutObject(); | 
| +        // TODO(liberato): update tests to include audio. | 
| +        // TODO(liberato): fix visibility check for "onscreen". | 
| +    if (layoutObject && layoutObject->isMedia()) { | 
| +        LayoutMedia* layoutMedia = static_cast<LayoutMedia*>(layoutObject); | 
| +        layoutMedia->setRequestPositionUpdates(true); | 
| +        m_registeredWithView = true; | 
| +    } | 
| +} | 
| + | 
| +void AutoplayExperimentHelper::unregisterForPositionUpdatesIfNeeded() | 
| +{ | 
| +    if (m_registeredWithView) { | 
| +        LayoutObject* obj = m_element.layoutObject(); | 
| +        if (obj && obj->isMedia()) { | 
| +            LayoutMedia* layoutMedia = (LayoutMedia*)obj; | 
| +            layoutMedia->setRequestPositionUpdates(false); | 
| +            m_registeredWithView = false; | 
| +        } | 
| +    } | 
| +} | 
| + | 
| +void AutoplayExperimentHelper::positionChanged() | 
| +{ | 
| +    // Reset the timer to indicate that scrolling has happened | 
| +    // recently, and might still be ongoing. | 
| +    // 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. | 
| + | 
| +    LocationState curLocation(m_element); | 
| +    const bool inViewport = curLocation.isInViewport(); | 
| +    if (inViewport && !m_wasInViewport) { | 
| +        // We have transitioned from not visible to visible.  Reset the timer | 
| +        // to check if we should start autoplay. | 
| +        m_viewportTimer.startOneShot(viewportTimerPollDelay, FROM_HERE); | 
| +    } | 
| +    m_wasInViewport = inViewport; | 
| +} | 
| + | 
| +void AutoplayExperimentHelper::triggerAutoplayViewportCheck() | 
| +{ | 
| +    viewportTimerFired(nullptr); | 
| +} | 
| + | 
| +void AutoplayExperimentHelper::viewportTimerFired(Timer<AutoplayExperimentHelper>*) | 
| +{ | 
| +    // 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::isInViewportIfNeeded() | 
| +{ | 
| +    // We could check for eligibility here, but we skip it.  Some of our | 
| +    // callers need to do it separately, and we don't want to check more | 
| +    // than we need to. | 
| +    // Also remember that page visibility is assumed for clank. | 
| + | 
| +    // If viewport visibility isn't required, then it's visible enough. | 
| +    if (!enabled(AutoplayExperimentConfig::Mode::IfViewport)) | 
| +        return true; | 
| + | 
| +    return LocationState(m_element).isInViewport(); | 
| +} | 
| + | 
| bool AutoplayExperimentHelper::maybeStartPlaying() | 
| { | 
| // See if we're allowed to autoplay now. | 
| -    if (!isEligible()) { | 
| +    if (!isEligible() || !isInViewportIfNeeded()) { | 
| return false; | 
| } | 
|  | 
| @@ -162,6 +265,7 @@ void AutoplayExperimentHelper::prepareToPlay(AutoplayMetrics metric) | 
| // once.  Be sure to do this before muteIfNeeded(). | 
| m_element.removeUserGestureRequirement(); | 
|  | 
| +    unregisterForPositionUpdatesIfNeeded(); | 
| muteIfNeeded(); | 
|  | 
| // Record that this autoplayed without a user gesture.  This is normally | 
| @@ -177,4 +281,63 @@ Document& AutoplayExperimentHelper::document() const | 
| return m_element.document(); | 
| } | 
|  | 
| +AutoplayExperimentHelper::LocationState::LocationState(Element& element) | 
| +    : m_valid(false) | 
| +{ | 
| +    const LocalDOMWindow* domWindow = element.document().domWindow(); | 
| +    if (!domWindow) | 
| +        return; | 
| + | 
| +    // Get the page visibility. | 
| +    Frame* frame = domWindow->frame(); | 
| +    if (!frame) | 
| +        return; | 
| + | 
| +    Page* page = frame->page(); | 
| +    if (!page) | 
| +        return; | 
| + | 
| +    if (!element.layoutObject()) | 
| +        return; | 
| + | 
| +    const LayoutBox* elementBox = element.layoutObject()->enclosingBox(); | 
| +    if (!elementBox) | 
| +        return; | 
| + | 
| +    float zoom = elementBox->style()->effectiveZoom(); | 
| +    IntRect us(elementBox->offsetLeft().toInt() | 
| +        , elementBox->offsetTop().toInt() | 
| +        , elementBox->clientWidth().toInt() | 
| +        , elementBox->clientHeight().toInt()); | 
| +    IntRect screen(domWindow->scrollX()*zoom, domWindow->scrollY()*zoom, domWindow->innerWidth()*zoom, domWindow->innerHeight()*zoom); | 
| + | 
| +    m_visibilityState = page->visibilityState(); | 
| +    m_element = us; | 
| +    m_screen = screen; | 
| +    m_valid = true; | 
| +} | 
| + | 
| +bool AutoplayExperimentHelper::LocationState::isInViewport() | 
| +{ | 
| +    // Check if we're in the viewport. | 
| +    // TODO(liberato): fix this. | 
| +    return m_valid | 
| +        && m_visibilityState == PageVisibilityStateVisible | 
| +        && m_screen.contains(m_element); | 
| +} | 
| + | 
| +bool AutoplayExperimentHelper::LocationState::operator==(const LocationState& them) const | 
| +{ | 
| +    // If either state is not valid, then they are not equal. | 
| +    return m_valid && them.valid() | 
| +        && m_visibilityState == them.visibilityState() | 
| +        && m_screen == them.screen() | 
| +        && m_element == them.element(); | 
| +} | 
| + | 
| +bool AutoplayExperimentHelper::LocationState::operator!=(const LocationState& them) const | 
| +{ | 
| +    return !((*this) == them); | 
| +} | 
| + | 
| } | 
|  |