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