1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/picture_in_picture/picture_in_picture_service_impl.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/test/bind_test_util.h"
11 #include "build/build_config.h"
12 #include "content/common/media/media_player_delegate_messages.h"
13 #include "content/public/browser/overlay_window.h"
14 #include "content/public/browser/web_contents_delegate.h"
15 #include "content/public/common/content_client.h"
16 #include "content/test/test_content_browser_client.h"
17 #include "content/test/test_render_frame_host.h"
18 #include "content/test/test_render_view_host.h"
19 #include "content/test/test_web_contents.h"
20 #include "mojo/public/cpp/bindings/pending_remote.h"
21 #include "mojo/public/cpp/bindings/receiver.h"
22 #include "mojo/public/cpp/bindings/remote.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24
25 namespace content {
26
27 class DummyPictureInPictureSessionObserver
28 : public blink::mojom::PictureInPictureSessionObserver {
29 public:
30 DummyPictureInPictureSessionObserver() = default;
31 ~DummyPictureInPictureSessionObserver() final = default;
32
33 // Implementation of PictureInPictureSessionObserver.
OnWindowSizeChanged(const gfx::Size &)34 void OnWindowSizeChanged(const gfx::Size&) final {}
OnStopped()35 void OnStopped() final {}
36
37 private:
38 DISALLOW_COPY_AND_ASSIGN(DummyPictureInPictureSessionObserver);
39 };
40
41 class PictureInPictureDelegate : public WebContentsDelegate {
42 public:
43 PictureInPictureDelegate() = default;
44
45 MOCK_METHOD3(EnterPictureInPicture,
46 PictureInPictureResult(WebContents*,
47 const viz::SurfaceId&,
48 const gfx::Size&));
49
50 private:
51 DISALLOW_COPY_AND_ASSIGN(PictureInPictureDelegate);
52 };
53
54 class TestOverlayWindow : public OverlayWindow {
55 public:
56 TestOverlayWindow() = default;
~TestOverlayWindow()57 ~TestOverlayWindow() override {}
58
Create(PictureInPictureWindowController * controller)59 static std::unique_ptr<OverlayWindow> Create(
60 PictureInPictureWindowController* controller) {
61 return std::unique_ptr<OverlayWindow>(new TestOverlayWindow());
62 }
63
IsActive()64 bool IsActive() override { return false; }
Close()65 void Close() override {}
ShowInactive()66 void ShowInactive() override {}
Hide()67 void Hide() override {}
IsVisible()68 bool IsVisible() override { return false; }
IsAlwaysOnTop()69 bool IsAlwaysOnTop() override { return false; }
GetBounds()70 gfx::Rect GetBounds() override { return gfx::Rect(size_); }
UpdateVideoSize(const gfx::Size & natural_size)71 void UpdateVideoSize(const gfx::Size& natural_size) override {
72 size_ = natural_size;
73 }
SetPlaybackState(PlaybackState playback_state)74 void SetPlaybackState(PlaybackState playback_state) override {}
SetAlwaysHidePlayPauseButton(bool is_visible)75 void SetAlwaysHidePlayPauseButton(bool is_visible) override {}
SetSkipAdButtonVisibility(bool is_visible)76 void SetSkipAdButtonVisibility(bool is_visible) override {}
SetNextTrackButtonVisibility(bool is_visible)77 void SetNextTrackButtonVisibility(bool is_visible) override {}
SetPreviousTrackButtonVisibility(bool is_visible)78 void SetPreviousTrackButtonVisibility(bool is_visible) override {}
SetSurfaceId(const viz::SurfaceId & surface_id)79 void SetSurfaceId(const viz::SurfaceId& surface_id) override {}
GetLayerForTesting()80 cc::Layer* GetLayerForTesting() override { return nullptr; }
81
82 private:
83 gfx::Size size_;
84
85 DISALLOW_COPY_AND_ASSIGN(TestOverlayWindow);
86 };
87
88 class PictureInPictureTestBrowserClient : public TestContentBrowserClient {
89 public:
90 PictureInPictureTestBrowserClient() = default;
91 ~PictureInPictureTestBrowserClient() override = default;
92
CreateWindowForPictureInPicture(PictureInPictureWindowController * controller)93 std::unique_ptr<OverlayWindow> CreateWindowForPictureInPicture(
94 PictureInPictureWindowController* controller) override {
95 return TestOverlayWindow::Create(controller);
96 }
97 };
98
99 class PictureInPictureServiceImplTest : public RenderViewHostImplTestHarness {
100 public:
SetUp()101 void SetUp() override {
102 RenderViewHostImplTestHarness::SetUp();
103
104 SetBrowserClientForTesting(&browser_client_);
105
106 TestRenderFrameHost* render_frame_host = contents()->GetMainFrame();
107 render_frame_host->InitializeRenderFrameIfNeeded();
108
109 contents()->SetDelegate(&delegate_);
110
111 mojo::Remote<blink::mojom::PictureInPictureService> service_remote;
112 service_impl_ = PictureInPictureServiceImpl::CreateForTesting(
113 render_frame_host, service_remote.BindNewPipeAndPassReceiver());
114 }
115
TearDown()116 void TearDown() override {
117 RenderViewHostImplTestHarness::TearDown();
118 }
119
service()120 PictureInPictureServiceImpl& service() { return *service_impl_; }
121
delegate()122 PictureInPictureDelegate& delegate() { return delegate_; }
123
124 private:
125 PictureInPictureTestBrowserClient browser_client_;
126 PictureInPictureDelegate delegate_;
127 // Will be deleted when the frame is destroyed.
128 PictureInPictureServiceImpl* service_impl_;
129 };
130
131 // Flaky on Android. https://crbug.com/970866
132 #if defined(OS_ANDROID)
133 #define MAYBE_EnterPictureInPicture DISABLED_EnterPictureInPicture
134 #else
135 #define MAYBE_EnterPictureInPicture EnterPictureInPicture
136 #endif
137
TEST_F(PictureInPictureServiceImplTest,MAYBE_EnterPictureInPicture)138 TEST_F(PictureInPictureServiceImplTest, MAYBE_EnterPictureInPicture) {
139 const int kPlayerVideoOnlyId = 30;
140
141 DummyPictureInPictureSessionObserver observer;
142 mojo::Receiver<blink::mojom::PictureInPictureSessionObserver>
143 observer_receiver(&observer);
144 mojo::PendingRemote<blink::mojom::PictureInPictureSessionObserver>
145 observer_remote;
146 observer_receiver.Bind(observer_remote.InitWithNewPipeAndPassReceiver());
147
148 // If Picture-in-Picture there shouldn't be an active session.
149 EXPECT_FALSE(service().active_session_for_testing());
150
151 viz::SurfaceId surface_id =
152 viz::SurfaceId(viz::FrameSinkId(1, 1),
153 viz::LocalSurfaceId(
154 11, base::UnguessableToken::Deserialize(0x111111, 0)));
155
156 EXPECT_CALL(delegate(),
157 EnterPictureInPicture(contents(), surface_id, gfx::Size(42, 42)))
158 .WillRepeatedly(testing::Return(PictureInPictureResult::kSuccess));
159
160 mojo::Remote<blink::mojom::PictureInPictureSession> session_remote;
161 gfx::Size window_size;
162
163 service().StartSession(
164 kPlayerVideoOnlyId, surface_id, gfx::Size(42, 42),
165 true /* show_play_pause_button */, std::move(observer_remote),
166 base::BindLambdaForTesting(
167 [&](mojo::PendingRemote<blink::mojom::PictureInPictureSession> remote,
168 const gfx::Size& b) {
169 if (remote.is_valid())
170 session_remote.Bind(std::move(remote));
171 window_size = b;
172 }));
173
174 EXPECT_TRUE(service().active_session_for_testing());
175 EXPECT_TRUE(session_remote);
176 EXPECT_EQ(gfx::Size(42, 42), window_size);
177
178 // Picture-in-Picture media player id should not be reset when the media is
179 // destroyed (e.g. video stops playing). This allows the Picture-in-Picture
180 // window to continue to control the media.
181 contents()->GetMainFrame()->OnMessageReceived(
182 MediaPlayerDelegateHostMsg_OnMediaDestroyed(
183 contents()->GetMainFrame()->GetRoutingID(), kPlayerVideoOnlyId));
184 EXPECT_TRUE(service().active_session_for_testing());
185 }
186
TEST_F(PictureInPictureServiceImplTest,EnterPictureInPicture_NotSupported)187 TEST_F(PictureInPictureServiceImplTest, EnterPictureInPicture_NotSupported) {
188 const int kPlayerVideoOnlyId = 30;
189 mojo::PendingRemote<blink::mojom::PictureInPictureSessionObserver>
190 observer_remote;
191 EXPECT_FALSE(service().active_session_for_testing());
192
193 viz::SurfaceId surface_id =
194 viz::SurfaceId(viz::FrameSinkId(1, 1),
195 viz::LocalSurfaceId(
196 11, base::UnguessableToken::Deserialize(0x111111, 0)));
197
198 EXPECT_CALL(delegate(),
199 EnterPictureInPicture(contents(), surface_id, gfx::Size(42, 42)))
200 .WillRepeatedly(testing::Return(PictureInPictureResult::kNotSupported));
201
202 mojo::Remote<blink::mojom::PictureInPictureSession> session_remote;
203 gfx::Size window_size;
204
205 service().StartSession(
206 kPlayerVideoOnlyId, surface_id, gfx::Size(42, 42),
207 true /* show_play_pause_button */, std::move(observer_remote),
208 base::BindLambdaForTesting(
209 [&](mojo::PendingRemote<blink::mojom::PictureInPictureSession> remote,
210 const gfx::Size& b) {
211 if (remote.is_valid())
212 session_remote.Bind(std::move(remote));
213 window_size = b;
214 }));
215
216 EXPECT_FALSE(service().active_session_for_testing());
217 // The |session_remote| won't be bound because the |pending_remote| received
218 // in the StartSessionCallback will be invalid due to PictureInPictureSession
219 // not ever being created (meaning the the receiver won't be bound either).
220 EXPECT_FALSE(session_remote);
221 EXPECT_EQ(gfx::Size(), window_size);
222 }
223
224 } // namespace content
225