blob: 2b23396c20d3bd604512d91776c4c8bb86a972cf [file] [log] [blame]
// Copyright 2019 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/modules/picture_in_picture/picture_in_picture_controller_impl.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/gmock/include/gmock/gmock.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/dom/events/event.h"
#include "third_party/blink/renderer/core/frame/frame_test_helpers.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.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/mediastream/media_stream_component.h"
#include "third_party/blink/renderer/platform/mediastream/media_stream_descriptor.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::_;
namespace blink {
// The MockPictureInPictureSession 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 MockPictureInPictureSession
: public mojom::blink::PictureInPictureSession {
public:
MockPictureInPictureSession(
mojo::PendingReceiver<mojom::blink::PictureInPictureSession> receiver)
: receiver_(this, std::move(receiver)) {
ON_CALL(*this, Stop(_)).WillByDefault([](StopCallback callback) {
std::move(callback).Run();
});
}
~MockPictureInPictureSession() override = default;
MOCK_METHOD1(Stop, void(StopCallback));
MOCK_METHOD4(Update,
void(uint32_t,
const base::Optional<viz::SurfaceId>&,
const gfx::Size&,
bool));
private:
mojo::Receiver<mojom::blink::PictureInPictureSession> receiver_;
};
// The MockPictureInPictureService 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 MockPictureInPictureService
: public mojom::blink::PictureInPictureService {
public:
MockPictureInPictureService() {
// Setup default implementations.
ON_CALL(*this, StartSession(_, _, _, _, _, _, _))
.WillByDefault(testing::Invoke(
this, &MockPictureInPictureService::StartSessionInternal));
}
~MockPictureInPictureService() override = default;
void Bind(mojo::ScopedMessagePipeHandle handle) {
receiver_.Bind(mojo::PendingReceiver<mojom::blink::PictureInPictureService>(
std::move(handle)));
session_.reset(new MockPictureInPictureSession(
session_remote_.InitWithNewPipeAndPassReceiver()));
}
MOCK_METHOD7(
StartSession,
void(uint32_t,
mojo::PendingAssociatedRemote<media::mojom::blink::MediaPlayer>,
const base::Optional<viz::SurfaceId>&,
const gfx::Size&,
bool,
mojo::PendingRemote<mojom::blink::PictureInPictureSessionObserver>,
StartSessionCallback));
MockPictureInPictureSession& Session() { return *session_.get(); }
void StartSessionInternal(
uint32_t,
mojo::PendingAssociatedRemote<media::mojom::blink::MediaPlayer>,
const base::Optional<viz::SurfaceId>&,
const gfx::Size&,
bool,
mojo::PendingRemote<mojom::blink::PictureInPictureSessionObserver>,
StartSessionCallback callback) {
std::move(callback).Run(std::move(session_remote_), gfx::Size());
}
private:
mojo::Receiver<mojom::blink::PictureInPictureService> receiver_{this};
std::unique_ptr<MockPictureInPictureSession> session_;
mojo::PendingRemote<mojom::blink::PictureInPictureSession> session_remote_;
DISALLOW_COPY_AND_ASSIGN(MockPictureInPictureService);
};
class PictureInPictureControllerFrameClient
: public test::MediaStubLocalFrameClient {
public:
static PictureInPictureControllerFrameClient* Create(
std::unique_ptr<WebMediaPlayer> player) {
return MakeGarbageCollected<PictureInPictureControllerFrameClient>(
std::move(player));
}
explicit PictureInPictureControllerFrameClient(
std::unique_ptr<WebMediaPlayer> player)
: test::MediaStubLocalFrameClient(std::move(player)) {}
private:
DISALLOW_COPY_AND_ASSIGN(PictureInPictureControllerFrameClient);
};
class PictureInPictureControllerPlayer : public EmptyWebMediaPlayer {
public:
PictureInPictureControllerPlayer() = default;
~PictureInPictureControllerPlayer() final = default;
double Duration() const final {
if (infinity_duration_)
return std::numeric_limits<double>::infinity();
return EmptyWebMediaPlayer::Duration();
}
ReadyState GetReadyState() const final { return kReadyStateHaveMetadata; }
bool HasVideo() const final { return true; }
void set_infinity_duration(bool value) { infinity_duration_ = value; }
private:
bool infinity_duration_ = false;
DISALLOW_COPY_AND_ASSIGN(PictureInPictureControllerPlayer);
};
class PictureInPictureControllerTest : public PageTestBase {
public:
void SetUp() override {
PageTestBase::SetupPageWithClients(
nullptr, PictureInPictureControllerFrameClient::Create(
std::make_unique<PictureInPictureControllerPlayer>()));
GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
mojom::blink::PictureInPictureService::Name_,
WTF::BindRepeating(&MockPictureInPictureService::Bind,
WTF::Unretained(&mock_service_)));
video_ = MakeGarbageCollected<HTMLVideoElement>(GetDocument());
video_->SetReadyState(HTMLMediaElement::ReadyState::kHaveMetadata);
layer_ = cc::Layer::Create();
video_->SetCcLayerForTesting(layer_.get());
std::string test_name =
testing::UnitTest::GetInstance()->current_test_info()->name();
if (test_name.find("MediaSource") != std::string::npos) {
MediaStreamComponentVector dummy_tracks;
auto* descriptor = MakeGarbageCollected<MediaStreamDescriptor>(
dummy_tracks, dummy_tracks);
Video()->SetSrcObject(descriptor);
} else {
video_->SetSrc("http://example.com/foo.mp4");
}
test::RunPendingTasks();
}
void TearDown() override {
GetFrame().GetBrowserInterfaceBroker().SetBinderForTesting(
mojom::blink::PictureInPictureService::Name_, {});
}
HTMLVideoElement* Video() const { return video_.Get(); }
MockPictureInPictureService& Service() { return mock_service_; }
private:
Persistent<HTMLVideoElement> video_;
MockPictureInPictureService mock_service_;
scoped_refptr<cc::Layer> layer_;
};
TEST_F(PictureInPictureControllerTest, EnterPictureInPictureFiresEvent) {
EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
.PictureInPictureElement());
WebMediaPlayer* player = Video()->GetWebMediaPlayer();
EXPECT_CALL(Service(),
StartSession(player->GetDelegateId(), _, player->GetSurfaceId(),
player->NaturalSize(), true, _, _));
PictureInPictureControllerImpl::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kEnterpictureinpicture);
EXPECT_NE(nullptr, PictureInPictureControllerImpl::From(GetDocument())
.PictureInPictureElement());
}
TEST_F(PictureInPictureControllerTest, ExitPictureInPictureFiresEvent) {
EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
.PictureInPictureElement());
WebMediaPlayer* player = Video()->GetWebMediaPlayer();
EXPECT_CALL(Service(),
StartSession(player->GetDelegateId(), _, player->GetSurfaceId(),
player->NaturalSize(), true, _, _));
PictureInPictureControllerImpl::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
EXPECT_CALL(Service().Session(), Stop(_));
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kEnterpictureinpicture);
PictureInPictureControllerImpl::From(GetDocument())
.ExitPictureInPicture(Video(), nullptr);
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kLeavepictureinpicture);
EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
.PictureInPictureElement());
}
TEST_F(PictureInPictureControllerTest, StartObserving) {
EXPECT_FALSE(PictureInPictureControllerImpl::From(GetDocument())
.IsSessionObserverReceiverBoundForTesting());
WebMediaPlayer* player = Video()->GetWebMediaPlayer();
EXPECT_CALL(Service(),
StartSession(player->GetDelegateId(), _, player->GetSurfaceId(),
player->NaturalSize(), true, _, _));
PictureInPictureControllerImpl::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kEnterpictureinpicture);
EXPECT_TRUE(PictureInPictureControllerImpl::From(GetDocument())
.IsSessionObserverReceiverBoundForTesting());
}
TEST_F(PictureInPictureControllerTest, StopObserving) {
EXPECT_FALSE(PictureInPictureControllerImpl::From(GetDocument())
.IsSessionObserverReceiverBoundForTesting());
WebMediaPlayer* player = Video()->GetWebMediaPlayer();
EXPECT_CALL(Service(),
StartSession(player->GetDelegateId(), _, player->GetSurfaceId(),
player->NaturalSize(), true, _, _));
PictureInPictureControllerImpl::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
EXPECT_CALL(Service().Session(), Stop(_));
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kEnterpictureinpicture);
PictureInPictureControllerImpl::From(GetDocument())
.ExitPictureInPicture(Video(), nullptr);
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kLeavepictureinpicture);
EXPECT_FALSE(PictureInPictureControllerImpl::From(GetDocument())
.IsSessionObserverReceiverBoundForTesting());
}
TEST_F(PictureInPictureControllerTest, PlayPauseButton_InfiniteDuration) {
EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
.PictureInPictureElement());
Video()->DurationChanged(std::numeric_limits<double>::infinity(), false);
WebMediaPlayer* player = Video()->GetWebMediaPlayer();
EXPECT_CALL(Service(),
StartSession(player->GetDelegateId(), _, player->GetSurfaceId(),
player->NaturalSize(), false, _, _));
PictureInPictureControllerImpl::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kEnterpictureinpicture);
}
TEST_F(PictureInPictureControllerTest, PlayPauseButton_MediaSource) {
EXPECT_EQ(nullptr, PictureInPictureControllerImpl::From(GetDocument())
.PictureInPictureElement());
// The test automatically setup the WebMediaPlayer with a MediaSource based on
// the test name.
WebMediaPlayer* player = Video()->GetWebMediaPlayer();
EXPECT_CALL(Service(),
StartSession(player->GetDelegateId(), _, player->GetSurfaceId(),
player->NaturalSize(), false, _, _));
PictureInPictureControllerImpl::From(GetDocument())
.EnterPictureInPicture(Video(), nullptr /* options */,
nullptr /* promise */);
MakeGarbageCollected<WaitForEvent>(Video(),
event_type_names::kEnterpictureinpicture);
}
TEST_F(PictureInPictureControllerTest, PerformMediaPlayerAction) {
frame_test_helpers::WebViewHelper helper;
helper.Initialize();
WebLocalFrameImpl* frame = helper.LocalMainFrame();
Document* document = frame->GetFrame()->GetDocument();
Persistent<HTMLVideoElement> video =
MakeGarbageCollected<HTMLVideoElement>(*document);
document->body()->AppendChild(video);
IntPoint bounds = video->BoundsInViewport().Center();
// Performs the specified media player action on the media element at the
// given location.
frame->GetFrame()->MediaPlayerActionAtViewportPoint(
bounds, blink::mojom::MediaPlayerActionType::kPictureInPicture, true);
}
} // namespace blink