blob: 9f4182538d6199493afbd798a4a5f0b06cebf75b [file] [log] [blame]
// 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 "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/platform/autoplay.mojom-blink.h"
#include "third_party/blink/public/platform/web_media_player_source.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/media/html_audio_element.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/html/media/media_error.h"
#include "third_party/blink/renderer/core/html/track/audio_track_list.h"
#include "third_party/blink/renderer/core/html/track/video_track_list.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/testing/dummy_page_holder.h"
#include "third_party/blink/renderer/platform/network/network_state_notifier.h"
#include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
using ::testing::AnyNumber;
using ::testing::Return;
using ::testing::_;
namespace blink {
class MockWebMediaPlayer : public EmptyWebMediaPlayer {
public:
MOCK_CONST_METHOD0(HasAudio, bool());
MOCK_CONST_METHOD0(HasVideo, bool());
MOCK_CONST_METHOD0(Duration, double());
MOCK_CONST_METHOD0(CurrentTime, double());
MOCK_METHOD1(EnabledAudioTracksChanged, void(const WebVector<TrackId>&));
MOCK_METHOD1(SelectedVideoTrackChanged, void(TrackId*));
MOCK_METHOD3(
Load,
WebMediaPlayer::LoadTiming(LoadType load_type,
const blink::WebMediaPlayerSource& source,
CorsMode cors_mode));
MOCK_CONST_METHOD0(DidLazyLoad, bool());
};
class WebMediaStubLocalFrameClient : public EmptyLocalFrameClient {
public:
static WebMediaStubLocalFrameClient* Create(
std::unique_ptr<WebMediaPlayer> player) {
return MakeGarbageCollected<WebMediaStubLocalFrameClient>(
std::move(player));
}
WebMediaStubLocalFrameClient(std::unique_ptr<WebMediaPlayer> player)
: player_(std::move(player)) {}
std::unique_ptr<WebMediaPlayer> CreateWebMediaPlayer(
HTMLMediaElement&,
const WebMediaPlayerSource&,
WebMediaPlayerClient* client,
WebLayerTreeView*) override {
DCHECK(player_) << " Empty injected player - already used?";
return std::move(player_);
}
private:
std::unique_ptr<WebMediaPlayer> player_;
};
enum class MediaTestParam { kAudio, kVideo };
class HTMLMediaElementTest : public testing::TestWithParam<MediaTestParam> {
protected:
void SetUp() override {
// Sniff the media player pointer to facilitate mocking.
auto mock_media_player = std::make_unique<MockWebMediaPlayer>();
media_player_ = mock_media_player.get();
// Most tests do not care about this call, nor its return value. Those that
// do will clear this expectation and set custom expectations/returns.
EXPECT_CALL(*mock_media_player, HasAudio())
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*mock_media_player, HasVideo())
.WillRepeatedly(testing::Return(true));
EXPECT_CALL(*mock_media_player, Duration())
.WillRepeatedly(testing::Return(1.0));
EXPECT_CALL(*mock_media_player, CurrentTime())
.WillRepeatedly(testing::Return(0));
EXPECT_CALL(*mock_media_player, Load(_, _, _))
.Times(AnyNumber())
.WillRepeatedly(Return(WebMediaPlayer::LoadTiming::kImmediate));
EXPECT_CALL(*mock_media_player, DidLazyLoad)
.WillRepeatedly(testing::Return(false));
dummy_page_holder_ = DummyPageHolder::Create(
IntSize(), nullptr,
WebMediaStubLocalFrameClient::Create(std::move(mock_media_player)),
nullptr);
if (GetParam() == MediaTestParam::kAudio)
media_ = HTMLAudioElement::Create(dummy_page_holder_->GetDocument());
else
media_ = HTMLVideoElement::Create(dummy_page_holder_->GetDocument());
}
HTMLMediaElement* Media() const { return media_.Get(); }
void SetCurrentSrc(const AtomicString& src) {
KURL url(src);
Media()->current_src_ = url;
}
MockWebMediaPlayer* MockMediaPlayer() { return media_player_; }
bool WasAutoplayInitiated() { return Media()->WasAutoplayInitiated(); }
bool CouldPlayIfEnoughData() { return Media()->CouldPlayIfEnoughData(); }
bool ShouldDelayLoadEvent() { return Media()->should_delay_load_event_; }
void SetReadyState(HTMLMediaElement::ReadyState state) {
Media()->SetReadyState(state);
}
void SetError(MediaError* err) { Media()->MediaEngineError(err); }
void SimulateHighMediaEngagement() {
Media()->GetDocument().GetPage()->AddAutoplayFlags(
mojom::blink::kAutoplayFlagHighMediaEngagement);
}
bool HasLazyLoadObserver() const {
return !!Media()->lazy_load_visibility_observer_;
}
ExecutionContext* GetExecutionContext() const {
return &dummy_page_holder_->GetDocument();
}
private:
std::unique_ptr<DummyPageHolder> dummy_page_holder_;
Persistent<HTMLMediaElement> media_;
// Owned by WebMediaStubLocalFrameClient.
MockWebMediaPlayer* media_player_;
};
INSTANTIATE_TEST_CASE_P(Audio,
HTMLMediaElementTest,
testing::Values(MediaTestParam::kAudio));
INSTANTIATE_TEST_CASE_P(Video,
HTMLMediaElementTest,
testing::Values(MediaTestParam::kVideo));
TEST_P(HTMLMediaElementTest, effectiveMediaVolume) {
struct TestData {
double volume;
bool muted;
double effective_volume;
} test_data[] = {
{0.0, false, 0.0}, {0.5, false, 0.5}, {1.0, false, 1.0},
{0.0, true, 0.0}, {0.5, true, 0.0}, {1.0, true, 0.0},
};
for (const auto& data : test_data) {
Media()->setVolume(data.volume);
Media()->setMuted(data.muted);
EXPECT_EQ(data.effective_volume, Media()->EffectiveMediaVolume());
}
}
enum class TestURLScheme {
kHttp,
kHttps,
kFtp,
kFile,
kData,
kBlob,
};
AtomicString SrcSchemeToURL(TestURLScheme scheme) {
switch (scheme) {
case TestURLScheme::kHttp:
return "http://example.com/foo.mp4";
case TestURLScheme::kHttps:
return "https://example.com/foo.mp4";
case TestURLScheme::kFtp:
return "ftp://example.com/foo.mp4";
case TestURLScheme::kFile:
return "file:///foo/bar.mp4";
case TestURLScheme::kData:
return "data:video/mp4;base64,XXXXXXX";
case TestURLScheme::kBlob:
return "blob:http://example.com/00000000-0000-0000-0000-000000000000";
default:
NOTREACHED();
}
return g_empty_atom;
}
TEST_P(HTMLMediaElementTest, preloadType) {
struct TestData {
bool data_saver_enabled;
bool force_preload_none_for_media_elements;
bool is_cellular;
TestURLScheme src_scheme;
AtomicString preload_to_set;
AtomicString preload_expected;
} test_data[] = {
// Tests for conditions in which preload type should be overriden to
// "none".
{false, true, false, TestURLScheme::kHttp, "auto", "none"},
{true, true, false, TestURLScheme::kHttps, "auto", "none"},
{true, true, false, TestURLScheme::kFtp, "metadata", "none"},
{false, false, false, TestURLScheme::kHttps, "auto", "auto"},
{false, true, false, TestURLScheme::kFile, "auto", "auto"},
{false, true, false, TestURLScheme::kData, "metadata", "metadata"},
{false, true, false, TestURLScheme::kBlob, "auto", "auto"},
{false, true, false, TestURLScheme::kFile, "none", "none"},
// Tests for conditions in which preload type should be overriden to
// "metadata".
{false, false, true, TestURLScheme::kHttp, "auto", "metadata"},
{false, false, true, TestURLScheme::kHttp, "scheme", "metadata"},
{false, false, true, TestURLScheme::kHttp, "none", "none"},
// Tests that the preload is overriden to "metadata".
{false, false, false, TestURLScheme::kHttp, "foo", "metadata"},
};
int index = 0;
for (const auto& data : test_data) {
GetNetworkStateNotifier().SetSaveDataEnabledOverride(
data.data_saver_enabled);
Media()->GetDocument().GetSettings()->SetForcePreloadNoneForMediaElements(
data.force_preload_none_for_media_elements);
if (data.is_cellular) {
GetNetworkStateNotifier().SetNetworkConnectionInfoOverride(
true, WebConnectionType::kWebConnectionTypeCellular3G,
WebEffectiveConnectionType::kTypeUnknown, 1.0, 2.0);
} else {
GetNetworkStateNotifier().ClearOverride();
}
SetCurrentSrc(SrcSchemeToURL(data.src_scheme));
Media()->setPreload(data.preload_to_set);
EXPECT_EQ(data.preload_expected, Media()->preload())
<< "preload type differs at index" << index;
++index;
}
}
TEST_P(HTMLMediaElementTest, CouldPlayIfEnoughDataRespondsToPlay) {
EXPECT_FALSE(CouldPlayIfEnoughData());
Media()->Play();
EXPECT_TRUE(CouldPlayIfEnoughData());
}
TEST_P(HTMLMediaElementTest, CouldPlayIfEnoughDataRespondsToEnded) {
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
Media()->Play();
test::RunPendingTasks();
MockWebMediaPlayer* mock_wmpi =
reinterpret_cast<MockWebMediaPlayer*>(Media()->GetWebMediaPlayer());
EXPECT_NE(mock_wmpi, nullptr);
EXPECT_TRUE(CouldPlayIfEnoughData());
// Playback can only end once the ready state is above kHaveMetadata.
SetReadyState(HTMLMediaElement::kHaveFutureData);
EXPECT_FALSE(Media()->paused());
EXPECT_FALSE(Media()->ended());
EXPECT_TRUE(CouldPlayIfEnoughData());
// Now advance current time to duration and verify ended state.
testing::Mock::VerifyAndClearExpectations(mock_wmpi);
EXPECT_CALL(*mock_wmpi, CurrentTime())
.WillRepeatedly(testing::Return(Media()->duration()));
EXPECT_FALSE(CouldPlayIfEnoughData());
EXPECT_TRUE(Media()->ended());
}
TEST_P(HTMLMediaElementTest, CouldPlayIfEnoughDataRespondsToError) {
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
Media()->Play();
test::RunPendingTasks();
MockWebMediaPlayer* mock_wmpi =
reinterpret_cast<MockWebMediaPlayer*>(Media()->GetWebMediaPlayer());
EXPECT_NE(mock_wmpi, nullptr);
EXPECT_TRUE(CouldPlayIfEnoughData());
SetReadyState(HTMLMediaElement::kHaveMetadata);
EXPECT_FALSE(Media()->paused());
EXPECT_FALSE(Media()->ended());
EXPECT_TRUE(CouldPlayIfEnoughData());
SetError(MediaError::Create(MediaError::kMediaErrDecode, ""));
EXPECT_FALSE(CouldPlayIfEnoughData());
}
TEST_P(HTMLMediaElementTest, CouldPlayIfEnoughDataInfiniteStreamNeverEnds) {
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
Media()->Play();
test::RunPendingTasks();
EXPECT_CALL(*MockMediaPlayer(), Duration())
.WillRepeatedly(testing::Return(std::numeric_limits<double>::infinity()));
EXPECT_CALL(*MockMediaPlayer(), CurrentTime())
.WillRepeatedly(testing::Return(std::numeric_limits<double>::infinity()));
SetReadyState(HTMLMediaElement::kHaveMetadata);
EXPECT_FALSE(Media()->paused());
EXPECT_FALSE(Media()->ended());
EXPECT_TRUE(CouldPlayIfEnoughData());
}
TEST_P(HTMLMediaElementTest, AutoplayInitiated_DocumentActivation_Low_Gesture) {
// Setup is the following:
// - Policy: DocumentUserActivation (aka. unified autoplay)
// - MEI: low;
// - Frame received user gesture.
RuntimeEnabledFeatures::SetMediaEngagementBypassAutoplayPoliciesEnabled(true);
Media()->GetDocument().GetSettings()->SetAutoplayPolicy(
AutoplayPolicy::Type::kDocumentUserActivationRequired);
LocalFrame::NotifyUserActivation(Media()->GetDocument().GetFrame());
Media()->Play();
EXPECT_FALSE(WasAutoplayInitiated());
}
TEST_P(HTMLMediaElementTest,
AutoplayInitiated_DocumentActivation_High_Gesture) {
// Setup is the following:
// - Policy: DocumentUserActivation (aka. unified autoplay)
// - MEI: high;
// - Frame received user gesture.
RuntimeEnabledFeatures::SetMediaEngagementBypassAutoplayPoliciesEnabled(true);
Media()->GetDocument().GetSettings()->SetAutoplayPolicy(
AutoplayPolicy::Type::kDocumentUserActivationRequired);
SimulateHighMediaEngagement();
LocalFrame::NotifyUserActivation(Media()->GetDocument().GetFrame());
Media()->Play();
EXPECT_FALSE(WasAutoplayInitiated());
}
TEST_P(HTMLMediaElementTest,
AutoplayInitiated_DocumentActivation_High_NoGesture) {
// Setup is the following:
// - Policy: DocumentUserActivation (aka. unified autoplay)
// - MEI: high;
// - Frame did not receive user gesture.
RuntimeEnabledFeatures::SetMediaEngagementBypassAutoplayPoliciesEnabled(true);
Media()->GetDocument().GetSettings()->SetAutoplayPolicy(
AutoplayPolicy::Type::kDocumentUserActivationRequired);
SimulateHighMediaEngagement();
Media()->Play();
EXPECT_TRUE(WasAutoplayInitiated());
}
TEST_P(HTMLMediaElementTest, AutoplayInitiated_GestureRequired_Gesture) {
// Setup is the following:
// - Policy: user gesture is required.
// - Frame received a user gesture.
// - MEI doesn't matter as it's not used by the policy.
Media()->GetDocument().GetSettings()->SetAutoplayPolicy(
AutoplayPolicy::Type::kUserGestureRequired);
LocalFrame::NotifyUserActivation(Media()->GetDocument().GetFrame());
Media()->Play();
EXPECT_FALSE(WasAutoplayInitiated());
}
TEST_P(HTMLMediaElementTest, AutoplayInitiated_NoGestureRequired_Gesture) {
// Setup is the following:
// - Policy: no user gesture is required.
// - Frame received a user gesture.
// - MEI doesn't matter as it's not used by the policy.
Media()->GetDocument().GetSettings()->SetAutoplayPolicy(
AutoplayPolicy::Type::kNoUserGestureRequired);
LocalFrame::NotifyUserActivation(Media()->GetDocument().GetFrame());
Media()->Play();
EXPECT_FALSE(WasAutoplayInitiated());
}
TEST_P(HTMLMediaElementTest, AutoplayInitiated_NoGestureRequired_NoGesture) {
// Setup is the following:
// - Policy: no user gesture is required.
// - Frame did not receive a user gesture.
// - MEI doesn't matter as it's not used by the policy.
Media()->GetDocument().GetSettings()->SetAutoplayPolicy(
AutoplayPolicy::Type::kNoUserGestureRequired);
Media()->Play();
EXPECT_TRUE(WasAutoplayInitiated());
}
TEST_P(HTMLMediaElementTest,
DeferredMediaPlayerLoadDoesNotDelayWindowLoadEvent) {
// Source isn't really important, we just need something to let load algorithm
// run up to the point of calling WebMediaPlayer::Load().
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
// WebMediaPlayer will signal that it will defer loading to some later time.
testing::Mock::VerifyAndClearExpectations(MockMediaPlayer());
EXPECT_CALL(*MockMediaPlayer(), Load(_, _, _))
.WillOnce(Return(WebMediaPlayer::LoadTiming::kDeferred));
// Window's 'load' event starts out "delayed".
EXPECT_TRUE(ShouldDelayLoadEvent());
Media()->load();
test::RunPendingTasks();
// No longer delayed because WMP loading is deferred.
EXPECT_FALSE(ShouldDelayLoadEvent());
}
TEST_P(HTMLMediaElementTest, ImmediateMediaPlayerLoadDoesDelayWindowLoadEvent) {
// Source isn't really important, we just need something to let load algorithm
// run up to the point of calling WebMediaPlayer::Load().
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
// WebMediaPlayer will signal that it will do the load immediately.
EXPECT_CALL(*MockMediaPlayer(), Load(_, _, _))
.WillOnce(Return(WebMediaPlayer::LoadTiming::kImmediate));
// Window's 'load' event starts out "delayed".
EXPECT_TRUE(ShouldDelayLoadEvent());
Media()->load();
test::RunPendingTasks();
// Still delayed because WMP loading is not deferred.
EXPECT_TRUE(ShouldDelayLoadEvent());
}
TEST_P(HTMLMediaElementTest, DefaultTracksAreEnabled) {
// Default tracks should start enabled, not be created then enabled.
EXPECT_CALL(*MockMediaPlayer(), EnabledAudioTracksChanged(_)).Times(0);
EXPECT_CALL(*MockMediaPlayer(), SelectedVideoTrackChanged(_)).Times(0);
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
test::RunPendingTasks();
SetReadyState(HTMLMediaElement::kHaveFutureData);
ASSERT_EQ(1u, Media()->audioTracks().length());
ASSERT_EQ(1u, Media()->videoTracks().length());
EXPECT_TRUE(Media()->audioTracks().AnonymousIndexedGetter(0)->enabled());
EXPECT_TRUE(Media()->videoTracks().AnonymousIndexedGetter(0)->selected());
}
// Ensure a visibility observer is created for lazy loading.
TEST_P(HTMLMediaElementTest, VisibilityObserverCreatedForLazyLoad) {
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
test::RunPendingTasks();
EXPECT_CALL(*MockMediaPlayer(), DidLazyLoad()).WillRepeatedly(Return(true));
SetReadyState(HTMLMediaElement::kHaveFutureData);
EXPECT_EQ(HasLazyLoadObserver(), GetParam() == MediaTestParam::kVideo);
}
TEST_P(HTMLMediaElementTest, DomInteractive) {
EXPECT_FALSE(Media()->GetDocument().GetTiming().DomInteractive().is_null());
}
TEST_P(HTMLMediaElementTest, ContextPaused) {
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
Media()->Play();
test::RunPendingTasks();
SetReadyState(HTMLMediaElement::kHaveFutureData);
EXPECT_FALSE(Media()->paused());
GetExecutionContext()->PausePausableObjects(PauseState::kFrozen);
EXPECT_TRUE(Media()->paused());
GetExecutionContext()->UnpausePausableObjects();
EXPECT_FALSE(Media()->paused());
}
} // namespace blink