blob: 623c2851bdddb5cb80313b2007ee27f1f0e747cf [file] [log] [blame]
// Copyright 2018 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/video_wake_lock.h"
#include "cc/layers/layer.h"
#include "media/mojo/mojom/media_player.mojom-blink.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom-blink.h"
#include "third_party/blink/renderer/core/css_value_keywords.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/picture_in_picture_controller.h"
#include "third_party/blink/renderer/core/html/media/html_media_test_helper.h"
#include "third_party/blink/renderer/core/html/media/html_video_element.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/testing/wait_for_event.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
namespace blink {
// The VideoWakeLockPictureInPictureSession implements a PictureInPicture
// session in the same process as the test and guarantees that the callbacks are
// called in order for the events to be fired.
class VideoWakeLockPictureInPictureSession
: public mojom::blink::PictureInPictureSession {
public:
explicit VideoWakeLockPictureInPictureSession(
mojo::PendingReceiver<mojom::blink::PictureInPictureSession> receiver)
: receiver_(this, std::move(receiver)) {}
~VideoWakeLockPictureInPictureSession() override = default;
void Stop(StopCallback callback) final { std::move(callback).Run(); }
void Update(uint32_t player_id,
const base::Optional<viz::SurfaceId>&,
const gfx::Size&,
bool show_play_pause_button) final {}
private:
mojo::Receiver<mojom::blink::PictureInPictureSession> receiver_;
};
// The VideoWakeLockPictureInPictureService implements the PictureInPicture
// service in the same process as the test and guarantees that the callbacks are
// called in order for the events to be fired.
class VideoWakeLockPictureInPictureService
: public mojom::blink::PictureInPictureService {
public:
VideoWakeLockPictureInPictureService() : receiver_(this) {}
~VideoWakeLockPictureInPictureService() override = default;
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<mojom::blink::PictureInPictureService>(
std::move(handle)));
}
void StartSession(
uint32_t,
mojo::PendingAssociatedRemote<media::mojom::blink::MediaPlayer>,
const base::Optional<viz::SurfaceId>&,
const gfx::Size&,
bool,
mojo::PendingRemote<mojom::blink::PictureInPictureSessionObserver>,
StartSessionCallback callback) final {
mojo::PendingRemote<mojom::blink::PictureInPictureSession> session_remote;
session_.reset(new VideoWakeLockPictureInPictureSession(
session_remote.InitWithNewPipeAndPassReceiver()));
std::move(callback).Run(std::move(session_remote), gfx::Size());
}
private:
mojo::Receiver<mojom::blink::PictureInPictureService> receiver_;
std::unique_ptr<VideoWakeLockPictureInPictureSession> session_;
};
class VideoWakeLockMediaPlayer final : public EmptyWebMediaPlayer {
public:
ReadyState GetReadyState() const final { return kReadyStateHaveMetadata; }
bool HasVideo() const final { return true; }
};
class VideoWakeLockFrameClient : public test::MediaStubLocalFrameClient {
public:
explicit VideoWakeLockFrameClient(std::unique_ptr<WebMediaPlayer> player)
: test::MediaStubLocalFrameClient(std::move(player)) {}
private:
DISALLOW_COPY_AND_ASSIGN(VideoWakeLockFrameClient);
};
class VideoWakeLockTest : public PageTestBase {
public:
void SetUp() override {
PageTestBase::SetupPageWithClients(
nullptr, MakeGarbageCollected<VideoWakeLockFrameClient>(
std::make_unique<VideoWakeLockMediaPlayer>()));
GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
mojom::blink::PictureInPictureService::Name_,
WTF::BindRepeating(&VideoWakeLockPictureInPictureService::Bind,
WTF::Unretained(&pip_service_)));
GetDocument().body()->setInnerHTML("<body><video></video></body>");
video_ = To<HTMLVideoElement>(GetDocument().QuerySelector("video"));
video_->SetReadyState(HTMLMediaElement::ReadyState::kHaveMetadata);
video_wake_lock_ = MakeGarbageCollected<VideoWakeLock>(*video_.Get());
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
true);
}
void TearDown() override {
GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
mojom::blink::PictureInPictureService::Name_, {});
PageTestBase::TearDown();
}
HTMLVideoElement* Video() const { return video_.Get(); }
VideoWakeLock* GetVideoWakeLock() const { return video_wake_lock_.Get(); }
void SetFakeCcLayer(cc::Layer* layer) { video_->SetCcLayer(layer); }
void SimulatePlaying() {
video_wake_lock_->Invoke(GetFrame().DomWindow(),
Event::Create(event_type_names::kPlaying));
}
void SimulatePause() {
video_wake_lock_->Invoke(GetFrame().DomWindow(),
Event::Create(event_type_names::kPause));
}
void SimulateEnterPictureInPicture() {
PictureInPictureController::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
MakeGarbageCollected<WaitForEvent>(
video_.Get(), event_type_names::kEnterpictureinpicture);
}
void SimulateLeavePictureInPicture() {
PictureInPictureController::From(GetDocument())
.ExitPictureInPicture(Video(), nullptr);
MakeGarbageCollected<WaitForEvent>(
video_.Get(), event_type_names::kLeavepictureinpicture);
}
void SimulateContextPause() {
GetFrame().DomWindow()->SetLifecycleState(
mojom::FrameLifecycleState::kPaused);
}
void SimulateContextRunning() {
GetFrame().DomWindow()->SetLifecycleState(
mojom::FrameLifecycleState::kRunning);
}
void SimulateContextDestroyed() { GetFrame().DomWindow()->FrameDestroyed(); }
void SimulateNetworkState(HTMLMediaElement::NetworkState network_state) {
video_->SetNetworkState(network_state);
}
void UpdateVisibilityObserver() {
UpdateAllLifecyclePhasesForTest();
test::RunPendingTasks();
}
void HideVideo() {
video_->SetInlineStyleProperty(CSSPropertyID::kDisplay, CSSValueID::kNone);
}
void ShowVideo() {
video_->SetInlineStyleProperty(CSSPropertyID::kDisplay, CSSValueID::kBlock);
}
private:
Persistent<HTMLVideoElement> video_;
Persistent<VideoWakeLock> video_wake_lock_;
VideoWakeLockPictureInPictureService pip_service_;
};
TEST_F(VideoWakeLockTest, NoLockByDefault) {
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, PlayingVideoRequestsLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, PausingVideoCancelsLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulatePause();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HiddingPageCancelsLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, PlayingWhileHiddenDoesNotRequestLock) {
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, ShowingPageRequestsLock) {
SimulatePlaying();
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
false);
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, ShowingPageDoNotRequestsLockIfPaused) {
SimulatePlaying();
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
SimulatePause();
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kVisible,
false);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, RemotePlaybackDisconnectedDoesNotCancelLock) {
SimulatePlaying();
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CLOSED);
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, RemotePlaybackConnectingDoesNotCancelLock) {
SimulatePlaying();
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CONNECTING);
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, ActiveRemotePlaybackCancelsLock) {
SimulatePlaying();
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CLOSED);
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CONNECTED);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, LeavingRemotePlaybackResumesLock) {
SimulatePlaying();
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CONNECTED);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CLOSED);
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, PictureInPictureLocksWhenPageNotVisible) {
// This initialeses the video element in order to not crash when the
// interstitial tries to show itself and so that the WebMediaPlayer is set up.
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
SetFakeCcLayer(layer.get());
Video()->SetSrc("http://example.com/foo.mp4");
test::RunPendingTasks();
SimulatePlaying();
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
SimulateEnterPictureInPicture();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, PictureInPictureDoesNoLockWhenPaused) {
// This initialeses the video element in order to not crash when the
// interstitial tries to show itself and so that the WebMediaPlayer is set up.
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
SetFakeCcLayer(layer.get());
Video()->SetSrc("http://example.com/foo.mp4");
test::RunPendingTasks();
SimulatePlaying();
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
SimulatePause();
SimulateEnterPictureInPicture();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, LeavingPictureInPictureCancelsLock) {
// This initialeses the video element in order to not crash when the
// interstitial tries to show itself and so that the WebMediaPlayer is set up.
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
SetFakeCcLayer(layer.get());
Video()->SetSrc("http://example.com/foo.mp4");
test::RunPendingTasks();
SimulatePlaying();
GetPage().SetVisibilityState(mojom::blink::PageVisibilityState::kHidden,
false);
SimulateEnterPictureInPicture();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulateLeavePictureInPicture();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, RemotingVideoInPictureInPictureDoesNotRequestLock) {
// This initialeses the video element in order to not crash when the
// interstitial tries to show itself and so that the WebMediaPlayer is set up.
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
SetFakeCcLayer(layer.get());
Video()->SetSrc("http://example.com/foo.mp4");
test::RunPendingTasks();
SimulatePlaying();
SimulateEnterPictureInPicture();
GetVideoWakeLock()->OnRemotePlaybackStateChanged(
mojom::blink::PresentationConnectionState::CONNECTED);
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, PausingContextCancelsLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulateContextPause();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, ResumingContextResumesLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulateContextPause();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
SimulateContextRunning();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, DestroyingContextCancelsLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulateContextDestroyed();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, LoadingCancelsLock) {
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
// The network state has to be non-empty for the resetting to actually kick.
SimulateNetworkState(HTMLMediaElement::kNetworkIdle);
Video()->SetSrc("");
test::RunPendingTasks();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, MutedHiddenVideoDoesNotTakeLock) {
Video()->setMuted(true);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, AudibleHiddenVideoTakesLock) {
Video()->setMuted(false);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, UnmutingHiddenVideoTakesLock) {
Video()->setMuted(true);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
Video()->setMuted(false);
test::RunPendingTasks();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, MutingHiddenVideoReleasesLock) {
Video()->setMuted(false);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
Video()->setMuted(true);
test::RunPendingTasks();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HidingAudibleVideoDoesNotReleaseLock) {
Video()->setMuted(false);
ShowVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
HideVideo();
UpdateVisibilityObserver();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HidingMutedVideoReleasesLock) {
Video()->setMuted(true);
ShowVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
HideVideo();
UpdateVisibilityObserver();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
TEST_F(VideoWakeLockTest, HiddenMutedVideoAlwaysVisibleInPictureInPicture) {
// This initialeses the video element in order to not crash when the
// interstitial tries to show itself and so that the WebMediaPlayer is set up.
scoped_refptr<cc::Layer> layer = cc::Layer::Create();
SetFakeCcLayer(layer.get());
Video()->SetSrc("http://example.com/foo.mp4");
Video()->setMuted(true);
HideVideo();
UpdateVisibilityObserver();
SimulatePlaying();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
SimulateEnterPictureInPicture();
EXPECT_TRUE(GetVideoWakeLock()->active_for_tests());
SimulateLeavePictureInPicture();
EXPECT_FALSE(GetVideoWakeLock()->active_for_tests());
}
} // namespace blink