blob: 8c42fa191609ddcb6783f274ba2a200ea595c586 [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.
#include "core/dom/Document.h"
#include "core/dom/DocumentUserGestureToken.h"
#include "core/html/AutoplayExperimentHelper.h"
#include "platform/UserGestureIndicator.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
// msvc refuses to compile if we use all of ::testing, due to a conflict with
// WTF::NotNull. So, we just use what we need.
using ::testing::Return;
using ::testing::NiceMock;
using ::testing::_;
namespace blink {
class MockAutoplayClient : public AutoplayExperimentHelper::Client {
public:
enum ElementType { Video, Audio };
MockAutoplayClient(const char* mode, ElementType type)
: m_mode(mode), m_duration(100) {
// Set up default behaviors that the helper is allowed to use or cache
// during construction.
ON_CALL(*this, autoplayExperimentMode()).WillByDefault(Return(m_mode));
// Use m_isVideo to answer these.
ON_CALL(*this, isHTMLVideoElement()).WillByDefault(Return(type == Video));
ON_CALL(*this, isHTMLAudioElement()).WillByDefault(Return(type == Audio));
// Now set up some useful defaults.
// Remember that this are only evaluated once.
ON_CALL(*this, duration()).WillByDefault(Return(m_duration));
ON_CALL(*this, currentTime()).WillByDefault(Return(0));
// Default to "not optimized for mobile" page.
ON_CALL(*this, isLegacyViewportType()).WillByDefault(Return(false));
// Other handy defaults.
ON_CALL(*this, paused()).WillByDefault(Return(true));
ON_CALL(*this, ended()).WillByDefault(Return(false));
ON_CALL(*this, pageVisibilityState())
.WillByDefault(Return(PageVisibilityStateVisible));
ON_CALL(*this, isCrossOrigin()).WillByDefault(Return(false));
ON_CALL(*this, isAutoplayAllowedPerSettings()).WillByDefault(Return(true));
ON_CALL(*this, absoluteBoundingBoxRect())
.WillByDefault(Return(IntRect(10, 10, 100, 100)));
// Normally, the autoplay experiment should not modify lots of other
// state unless we explicitly expect it.
EXPECT_CALL(*this, setMuted(_)).Times(0);
EXPECT_CALL(*this, unlockUserGesture()).Times(0);
EXPECT_CALL(*this, setRequestPositionUpdates(true)).Times(0);
EXPECT_CALL(*this, recordAutoplayMetric(_)).Times(0);
}
virtual ~MockAutoplayClient() {}
MOCK_CONST_METHOD0(currentTime, double());
MOCK_CONST_METHOD0(duration, double());
MOCK_CONST_METHOD0(paused, bool());
MOCK_CONST_METHOD0(ended, bool());
MOCK_CONST_METHOD0(muted, bool());
MOCK_METHOD1(setMuted, void(bool));
MOCK_METHOD0(playInternal, void());
MOCK_METHOD0(pauseInternal, void());
MOCK_CONST_METHOD0(isLockedPendingUserGesture, bool());
MOCK_METHOD0(unlockUserGesture, void());
MOCK_METHOD1(recordAutoplayMetric, void(AutoplayMetrics));
MOCK_METHOD0(shouldAutoplay, bool());
MOCK_CONST_METHOD0(isHTMLVideoElement, bool());
MOCK_CONST_METHOD0(isHTMLAudioElement, bool());
MOCK_METHOD0(isLegacyViewportType, bool());
MOCK_CONST_METHOD0(pageVisibilityState, PageVisibilityState());
MOCK_CONST_METHOD0(autoplayExperimentMode, String());
MOCK_CONST_METHOD0(isCrossOrigin, bool());
MOCK_CONST_METHOD0(isAutoplayAllowedPerSettings, bool());
MOCK_METHOD1(setRequestPositionUpdates, void(bool));
MOCK_CONST_METHOD0(absoluteBoundingBoxRect, IntRect());
const char* m_mode;
// const since changes to it won't affect the mocked value.
const double m_duration;
};
class AutoplayExperimentTest : public ::testing::Test {
public:
AutoplayExperimentTest() {}
~AutoplayExperimentTest() {}
bool isEligible() { return m_helper->isEligible(); }
bool meetsVisibilityRequirements() {
return m_helper->meetsVisibilityRequirements();
}
void setInterface(MockAutoplayClient* client) {
m_client = client;
// Set some defaults.
setUserGestureRequiredForPlay(true);
setShouldAutoplay(true);
setIsMuted(false);
m_helper = AutoplayExperimentHelper::create(m_client.get());
}
void TearDown() {
// Be sure that the mock is destructed before the test, so that any
// missing expectations are noticed. Otherwise, the test will pass
// and then missing expectations will show up in the log, without
// causing a test failure.
m_helper.clear();
m_client.clear();
ThreadState::current()->collectAllGarbage();
}
Persistent<MockAutoplayClient> m_client;
Persistent<AutoplayExperimentHelper> m_helper;
// Mirror updatePlayState to transition to play.
void startPlayback() {
EXPECT_CALL(*m_client, recordAutoplayMetric(AnyPlaybackStarted)).Times(1);
m_helper->playbackStarted();
}
void startPlaybackWithoutUserGesture() {
EXPECT_FALSE(UserGestureIndicator::processingUserGesture());
startPlayback();
}
void startPlaybackWithUserGesture() {
UserGestureIndicator indicator(DocumentUserGestureToken::create(nullptr));
EXPECT_TRUE(UserGestureIndicator::processingUserGesture());
startPlayback();
}
void setUserGestureRequiredForPlay(bool required) {
ON_CALL(*m_client, isLockedPendingUserGesture())
.WillByDefault(Return(required));
}
void setShouldAutoplay(bool should) {
ON_CALL(*m_client, shouldAutoplay()).WillByDefault(Return(should));
}
void setIsMuted(bool isMuted) {
ON_CALL(*m_client, muted()).WillByDefault(Return(isMuted));
}
void pausePlaybackExpectingBailout(bool expectingAutoplay) {
EXPECT_CALL(*m_client, recordAutoplayMetric(AnyPlaybackPaused)).Times(1);
EXPECT_CALL(*m_client, recordAutoplayMetric(AnyPlaybackBailout)).Times(1);
if (expectingAutoplay) {
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayPaused)).Times(1);
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayBailout)).Times(1);
}
m_helper->pauseMethodCalled();
m_helper->playbackStopped();
}
void pausePlaybackNotExpectingBailout(bool expectingAutoplay) {
EXPECT_CALL(*m_client, recordAutoplayMetric(AnyPlaybackPaused)).Times(1);
if (expectingAutoplay)
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayPaused)).Times(1);
EXPECT_CALL(*m_client, recordAutoplayMetric(AnyPlaybackBailout)).Times(0);
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayBailout)).Times(0);
// Move past the bailout point, but not to the end. That way, we don't
// have to set ended().
ON_CALL(*m_client, currentTime())
.WillByDefault(Return(m_client->m_duration - 1));
m_helper->pauseMethodCalled();
m_helper->playbackStopped();
}
void endPlayback(bool expectingAutoplay) {
EXPECT_CALL(*m_client, recordAutoplayMetric(AnyPlaybackComplete)).Times(1);
if (expectingAutoplay)
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayComplete)).Times(1);
ON_CALL(*m_client, currentTime())
.WillByDefault(Return(m_client->m_duration));
ON_CALL(*m_client, ended()).WillByDefault(Return(true));
m_helper->playbackStopped();
}
void moveIntoViewport() {
m_helper->positionChanged(IntRect(0, 0, 200, 200));
m_helper->triggerAutoplayViewportCheckForTesting();
}
};
TEST_F(AutoplayExperimentTest, IsNotEligibleWithEmptyMode) {
setInterface(new NiceMock<MockAutoplayClient>("", MockAutoplayClient::Video));
EXPECT_FALSE(isEligible());
}
TEST_F(AutoplayExperimentTest, IsVideoEligibleForVideoMode) {
// Video should be eligible in "forvideo" mode.
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
EXPECT_TRUE(isEligible());
}
TEST_F(AutoplayExperimentTest, IsAudioNotEligibleForVideoMode) {
// Audio should not be eligible for video mode.
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Audio));
EXPECT_FALSE(isEligible());
}
TEST_F(AutoplayExperimentTest, IsEligibleRequiresUserGesture) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
// If a user gesture is not required, then we're not eligible.
ON_CALL(*m_client, isLockedPendingUserGesture()).WillByDefault(Return(false));
EXPECT_FALSE(isEligible());
}
TEST_F(AutoplayExperimentTest, IsEligibleRequiresShouldAutoplay) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
// If we shouldn't autoplay, then we're not eligible.
ON_CALL(*m_client, shouldAutoplay()).WillByDefault(Return(false));
EXPECT_FALSE(isEligible());
}
TEST_F(AutoplayExperimentTest, IsAudioEligibleForAudioMode) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-foraudio",
MockAutoplayClient::Audio));
EXPECT_TRUE(isEligible());
}
TEST_F(AutoplayExperimentTest, EligibleIfOptimizedForMobile) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo-ifmobile",
MockAutoplayClient::Video));
// Should not be eligible with our default of "not mobile".
EXPECT_FALSE(isEligible());
ON_CALL(*m_client, isLegacyViewportType()).WillByDefault(Return(true));
EXPECT_TRUE(isEligible());
}
TEST_F(AutoplayExperimentTest, EligibleIfMuted) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo-ifmuted",
MockAutoplayClient::Video));
// Should not be eligible with our default of "not muted".
EXPECT_FALSE(isEligible());
ON_CALL(*m_client, muted()).WillByDefault(Return(true));
EXPECT_TRUE(isEligible());
}
TEST_F(AutoplayExperimentTest, BecameReadyAutoplayThenBailout) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->becameReadyToPlay();
EXPECT_TRUE(m_helper->isGestureRequirementOverridden());
EXPECT_CALL(
*m_client,
recordAutoplayMetric(GesturelessPlaybackStartedByAutoplayFlagImmediately))
.Times(1);
startPlaybackWithoutUserGesture();
pausePlaybackExpectingBailout(true);
}
TEST_F(AutoplayExperimentTest, BecameReadyAutoplayThenPause) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->becameReadyToPlay();
EXPECT_TRUE(m_helper->isGestureRequirementOverridden());
EXPECT_CALL(
*m_client,
recordAutoplayMetric(GesturelessPlaybackStartedByAutoplayFlagImmediately))
.Times(1);
startPlaybackWithoutUserGesture();
pausePlaybackNotExpectingBailout(true);
}
TEST_F(AutoplayExperimentTest, BecameReadyAutoplayThenComplete) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->becameReadyToPlay();
EXPECT_TRUE(m_helper->isGestureRequirementOverridden());
EXPECT_CALL(
*m_client,
recordAutoplayMetric(GesturelessPlaybackStartedByAutoplayFlagImmediately))
.Times(1);
startPlaybackWithoutUserGesture();
// Now stop at the end.
endPlayback(true);
}
TEST_F(AutoplayExperimentTest, NoUserGestureNeededShouldNotOverride) {
// Make sure that we don't override the user gesture if it isn't needed.
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
setUserGestureRequiredForPlay(false);
// It is still autoplay media, though.
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->becameReadyToPlay();
EXPECT_CALL(*m_client, recordAutoplayMetric(GesturelessPlaybackNotOverridden))
.Times(1);
startPlaybackWithoutUserGesture();
}
TEST_F(AutoplayExperimentTest, NoAutoplayMetricsIfNoAutoplay) {
// If playback is started while processing a user gesture, then nothing
// should be overridden or logged about autoplay.
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
setUserGestureRequiredForPlay(false);
setShouldAutoplay(false);
startPlaybackWithUserGesture();
// Expect bailout, but not from autoplay.
pausePlaybackExpectingBailout(false);
}
TEST_F(AutoplayExperimentTest, PlayMethodThenBailout) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
setShouldAutoplay(false); // No autoplay attribute.
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->playMethodCalled();
EXPECT_TRUE(m_helper->isGestureRequirementOverridden());
EXPECT_CALL(*m_client, recordAutoplayMetric(
GesturelessPlaybackStartedByPlayMethodImmediately))
.Times(1);
startPlaybackWithoutUserGesture();
pausePlaybackExpectingBailout(true);
}
TEST_F(AutoplayExperimentTest, DeferAutoplayUntilMuted) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo-ifmuted",
MockAutoplayClient::Video));
// Should not override the gesture requirement yet.
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->becameReadyToPlay();
// When we toggle the muted attribute, it should become eligible to start.
EXPECT_FALSE(m_helper->isGestureRequirementOverridden());
setIsMuted(true);
EXPECT_TRUE(m_helper->isGestureRequirementOverridden());
}
TEST_F(AutoplayExperimentTest, DeferPlaybackUntilInViewport) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo-ifviewport",
MockAutoplayClient::Video));
// Should not override the gesture requirement yet.
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
EXPECT_CALL(*m_client, setRequestPositionUpdates(true)).Times(1);
m_helper->becameReadyToPlay();
EXPECT_CALL(*m_client, playInternal()).Times(1);
EXPECT_CALL(*m_client, setRequestPositionUpdates(false)).Times(1);
moveIntoViewport();
EXPECT_TRUE(m_helper->isGestureRequirementOverridden());
}
TEST_F(AutoplayExperimentTest, WithSameOriginTests) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo-ifsameorigin",
MockAutoplayClient::Video));
ON_CALL(*m_client, isCrossOrigin()).WillByDefault(Return(false));
EXPECT_TRUE(isEligible());
ON_CALL(*m_client, isCrossOrigin()).WillByDefault(Return(true));
EXPECT_FALSE(isEligible());
}
TEST_F(AutoplayExperimentTest, WithoutSameOriginTests) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
ON_CALL(*m_client, isCrossOrigin()).WillByDefault(Return(false));
EXPECT_TRUE(isEligible());
ON_CALL(*m_client, isCrossOrigin()).WillByDefault(Return(true));
EXPECT_TRUE(isEligible());
}
TEST_F(AutoplayExperimentTest, AudioPageVisibility) {
setInterface(new NiceMock<MockAutoplayClient>(
"enabled-foraudio-ifpagevisible", MockAutoplayClient::Audio));
ON_CALL(*m_client, pageVisibilityState())
.WillByDefault(Return(PageVisibilityStateVisible));
EXPECT_TRUE(isEligible());
EXPECT_TRUE(meetsVisibilityRequirements());
ON_CALL(*m_client, pageVisibilityState())
.WillByDefault(Return(PageVisibilityStateHidden));
EXPECT_TRUE(isEligible());
EXPECT_FALSE(meetsVisibilityRequirements());
}
TEST_F(AutoplayExperimentTest, PlayTwiceIsIgnored) {
setInterface(new NiceMock<MockAutoplayClient>("enabled-forvideo",
MockAutoplayClient::Video));
setShouldAutoplay(false); // No autoplay attribute.
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->playMethodCalled();
ON_CALL(*m_client, paused()).WillByDefault(Return(false));
m_helper->playMethodCalled();
}
TEST_F(AutoplayExperimentTest, CrossOriginMutedTests) {
setInterface(new NiceMock<MockAutoplayClient>(
"enabled-forvideo-ifsameorigin-ormuted", MockAutoplayClient::Video));
ON_CALL(*m_client, isCrossOrigin()).WillByDefault(Return(true));
// Cross-orgin unmuted content should be eligible.
setIsMuted(true);
EXPECT_TRUE(isEligible());
// Cross-origin muted content should not be eligible.
setIsMuted(false);
EXPECT_FALSE(isEligible());
// Start playback.
EXPECT_CALL(*m_client, recordAutoplayMetric(AutoplayMediaFound)).Times(1);
m_helper->becameReadyToPlay();
ON_CALL(*m_client, paused()).WillByDefault(Return(false));
setIsMuted(true);
m_helper->mutedChanged();
// Verify that unmuting pauses playback.
setIsMuted(false);
EXPECT_CALL(*m_client, pauseInternal()).Times(1);
m_helper->mutedChanged();
}
}