1 // Copyright 2017 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/public/browser/media_session.h"
6
7 #include "base/callback_helpers.h"
8 #include "base/command_line.h"
9 #include "base/optional.h"
10 #include "base/run_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/synchronization/lock.h"
13 #include "base/test/scoped_feature_list.h"
14 #include "build/build_config.h"
15 #include "content/browser/renderer_host/render_frame_host_impl.h"
16 #include "content/public/browser/render_frame_host.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/browser/web_contents_observer.h"
19 #include "content/public/common/content_features.h"
20 #include "content/public/test/browser_test.h"
21 #include "content/public/test/browser_test_utils.h"
22 #include "content/public/test/content_browser_test.h"
23 #include "content/public/test/content_browser_test_utils.h"
24 #include "content/public/test/media_start_stop_observer.h"
25 #include "content/public/test/test_utils.h"
26 #include "content/shell/browser/shell.h"
27 #include "media/base/media_switches.h"
28 #include "net/dns/mock_host_resolver.h"
29 #include "net/test/embedded_test_server/http_request.h"
30 #include "services/media_session/public/cpp/features.h"
31 #include "services/media_session/public/cpp/test/audio_focus_test_util.h"
32 #include "services/media_session/public/cpp/test/mock_media_session.h"
33
34 namespace content {
35
36 namespace {
37
38 const char kMediaSessionImageTestURL[] = "/media/session/image_test_page.html";
39 const char kMediaSessionImageTestPageVideoElement[] = "video";
40
41 const char kMediaSessionTestImagePath[] = "/media/session/test_image.jpg";
42
43 class MediaImageGetterHelper {
44 public:
MediaImageGetterHelper(content::MediaSession * media_session,const media_session::MediaImage & image,int min_size,int desired_size)45 MediaImageGetterHelper(content::MediaSession* media_session,
46 const media_session::MediaImage& image,
47 int min_size,
48 int desired_size) {
49 media_session->GetMediaImageBitmap(
50 image, min_size, desired_size,
51 base::BindOnce(&MediaImageGetterHelper::OnComplete,
52 base::Unretained(this)));
53 }
54
Wait()55 void Wait() {
56 if (bitmap_.has_value())
57 return;
58
59 run_loop_.Run();
60 }
61
bitmap()62 const SkBitmap& bitmap() { return *bitmap_; }
63
64 private:
OnComplete(const SkBitmap & bitmap)65 void OnComplete(const SkBitmap& bitmap) {
66 bitmap_ = bitmap;
67 run_loop_.Quit();
68 }
69
70 base::RunLoop run_loop_;
71 base::Optional<SkBitmap> bitmap_;
72
73 DISALLOW_COPY_AND_ASSIGN(MediaImageGetterHelper);
74 };
75
76 // Integration tests for content::MediaSession that do not take into
77 // consideration the implementation details contrary to
78 // MediaSessionImplBrowserTest.
79 class MediaSessionBrowserTestBase : public ContentBrowserTest {
80 public:
MediaSessionBrowserTestBase()81 MediaSessionBrowserTestBase() {
82 embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
83 &MediaSessionBrowserTestBase::OnServerRequest, base::Unretained(this)));
84 }
85
SetUp()86 void SetUp() override {
87 ContentBrowserTest::SetUp();
88 visited_urls_.clear();
89 }
90
SetUpCommandLine(base::CommandLine * command_line)91 void SetUpCommandLine(base::CommandLine* command_line) override {
92 command_line->AppendSwitchASCII(
93 switches::kAutoplayPolicy,
94 switches::autoplay::kNoUserGestureRequiredPolicy);
95 }
96
StartPlaybackAndWait(Shell * shell,const std::string & id)97 void StartPlaybackAndWait(Shell* shell, const std::string& id) {
98 shell->web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
99 base::ASCIIToUTF16("document.querySelector('#" + id + "').play();"),
100 base::NullCallback());
101 WaitForStart(shell);
102 }
103
StopPlaybackAndWait(Shell * shell,const std::string & id)104 void StopPlaybackAndWait(Shell* shell, const std::string& id) {
105 shell->web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
106 base::ASCIIToUTF16("document.querySelector('#" + id + "').pause();"),
107 base::NullCallback());
108 WaitForStop(shell);
109 }
110
WaitForStart(Shell * shell)111 void WaitForStart(Shell* shell) {
112 MediaStartStopObserver observer(shell->web_contents(),
113 MediaStartStopObserver::Type::kStart);
114 observer.Wait();
115 }
116
WaitForStop(Shell * shell)117 void WaitForStop(Shell* shell) {
118 MediaStartStopObserver observer(shell->web_contents(),
119 MediaStartStopObserver::Type::kStop);
120 observer.Wait();
121 }
122
IsPlaying(Shell * shell,const std::string & id)123 bool IsPlaying(Shell* shell, const std::string& id) {
124 bool result;
125 EXPECT_TRUE(
126 ExecuteScriptAndExtractBool(shell->web_contents(),
127 "window.domAutomationController.send("
128 "!document.querySelector('#" +
129 id + "').paused);",
130 &result));
131 return result;
132 }
133
WasURLVisited(const GURL & url)134 bool WasURLVisited(const GURL& url) {
135 base::AutoLock lock(visited_urls_lock_);
136 return base::Contains(visited_urls_, url);
137 }
138
SetupMediaImageTest()139 MediaSession* SetupMediaImageTest() {
140 EXPECT_TRUE(NavigateToURL(
141 shell(), embedded_test_server()->GetURL(kMediaSessionImageTestURL)));
142 StartPlaybackAndWait(shell(), kMediaSessionImageTestPageVideoElement);
143
144 MediaSession* media_session = MediaSession::Get(shell()->web_contents());
145
146 std::vector<media_session::MediaImage> expected_images;
147 expected_images.push_back(CreateTestImageWithSize(1));
148 expected_images.push_back(CreateTestImageWithSize(10));
149
150 media_session::test::MockMediaSessionMojoObserver observer(*media_session);
151 observer.WaitForExpectedImagesOfType(
152 media_session::mojom::MediaSessionImageType::kArtwork, expected_images);
153
154 return media_session;
155 }
156
CreateTestImageWithSize(int size) const157 media_session::MediaImage CreateTestImageWithSize(int size) const {
158 media_session::MediaImage image;
159 image.src = GetTestImageURL();
160 image.type = base::ASCIIToUTF16("image/jpeg");
161 image.sizes.push_back(gfx::Size(size, size));
162 return image;
163 }
164
GetTestImageURL() const165 GURL GetTestImageURL() const {
166 return embedded_test_server()->GetURL(kMediaSessionTestImagePath);
167 }
168
169 private:
OnServerRequest(const net::test_server::HttpRequest & request)170 void OnServerRequest(const net::test_server::HttpRequest& request) {
171 // Note this method is called on the EmbeddedTestServer's background thread.
172 base::AutoLock lock(visited_urls_lock_);
173 visited_urls_.insert(request.GetURL());
174 }
175
176 // visited_urls_ is accessed both on the main thread and on the
177 // EmbeddedTestServer's background thread via OnServerRequest(), so it must be
178 // locked.
179 base::Lock visited_urls_lock_;
180 std::set<GURL> visited_urls_;
181
182 DISALLOW_COPY_AND_ASSIGN(MediaSessionBrowserTestBase);
183 };
184
185 class MediaSessionBrowserTest : public MediaSessionBrowserTestBase {
186 public:
MediaSessionBrowserTest()187 MediaSessionBrowserTest() {
188 feature_list_.InitAndEnableFeature(media::kInternalMediaSession);
189 }
190
191 private:
192 base::test::ScopedFeatureList feature_list_;
193 };
194
195 class MediaSessionBrowserTestWithoutInternalMediaSession
196 : public MediaSessionBrowserTestBase {
197 public:
MediaSessionBrowserTestWithoutInternalMediaSession()198 MediaSessionBrowserTestWithoutInternalMediaSession() {
199 disabled_feature_list_.InitWithFeatures(
200 {}, {media::kInternalMediaSession,
201 media_session::features::kMediaSessionService});
202 }
203
204 private:
205 base::test::ScopedFeatureList disabled_feature_list_;
206 };
207
208 // A MediaSessionBrowserTest with BackForwardCache enabled.
209 class MediaSessionBrowserTestWithBackForwardCache
210 : public MediaSessionBrowserTestBase {
211 public:
MediaSessionBrowserTestWithBackForwardCache()212 MediaSessionBrowserTestWithBackForwardCache() {
213 feature_list_.InitWithFeaturesAndParameters(
214 {{features::kBackForwardCache,
215 {{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}},
216 {media::kInternalMediaSession, {}}},
217 /*disabled_features=*/{});
218 }
219
SetUpOnMainThread()220 void SetUpOnMainThread() override {
221 host_resolver()->AddRule("*", "127.0.0.1");
222 MediaSessionBrowserTestBase::SetUpOnMainThread();
223 }
224
225 private:
226 base::test::ScopedFeatureList feature_list_;
227 };
228
229 } // anonymous namespace
230
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithoutInternalMediaSession,MediaSessionNoOpWhenDisabled)231 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithoutInternalMediaSession,
232 MediaSessionNoOpWhenDisabled) {
233 EXPECT_TRUE(NavigateToURL(shell(),
234 GetTestUrl("media/session", "media-session.html")));
235
236 MediaSession* media_session = MediaSession::Get(shell()->web_contents());
237 ASSERT_NE(nullptr, media_session);
238
239 StartPlaybackAndWait(shell(), "long-video");
240 StartPlaybackAndWait(shell(), "long-audio");
241
242 media_session->Suspend(MediaSession::SuspendType::kSystem);
243 StopPlaybackAndWait(shell(), "long-audio");
244
245 // At that point, only "long-audio" is paused.
246 EXPECT_FALSE(IsPlaying(shell(), "long-audio"));
247 EXPECT_TRUE(IsPlaying(shell(), "long-video"));
248 }
249
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,SimplePlayPause)250 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, SimplePlayPause) {
251 EXPECT_TRUE(NavigateToURL(shell(),
252 GetTestUrl("media/session", "media-session.html")));
253
254 MediaSession* media_session = MediaSession::Get(shell()->web_contents());
255 ASSERT_NE(nullptr, media_session);
256
257 StartPlaybackAndWait(shell(), "long-video");
258
259 media_session->Suspend(MediaSession::SuspendType::kSystem);
260 WaitForStop(shell());
261 EXPECT_FALSE(IsPlaying(shell(), "long-video"));
262
263 media_session->Resume(MediaSession::SuspendType::kSystem);
264 WaitForStart(shell());
265 EXPECT_TRUE(IsPlaying(shell(), "long-video"));
266 }
267
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,MultiplePlayersPlayPause)268 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MultiplePlayersPlayPause) {
269 EXPECT_TRUE(NavigateToURL(shell(),
270 GetTestUrl("media/session", "media-session.html")));
271
272 MediaSession* media_session = MediaSession::Get(shell()->web_contents());
273 ASSERT_NE(nullptr, media_session);
274
275 StartPlaybackAndWait(shell(), "long-video");
276 StartPlaybackAndWait(shell(), "long-audio");
277
278 media_session->Suspend(MediaSession::SuspendType::kSystem);
279 WaitForStop(shell());
280 EXPECT_FALSE(IsPlaying(shell(), "long-video"));
281 EXPECT_FALSE(IsPlaying(shell(), "long-audio"));
282
283 media_session->Resume(MediaSession::SuspendType::kSystem);
284 WaitForStart(shell());
285 EXPECT_TRUE(IsPlaying(shell(), "long-video"));
286 EXPECT_TRUE(IsPlaying(shell(), "long-audio"));
287 }
288
289 // Flaky on Mac. See https://crbug.com/980663
290 #if defined(OS_MAC)
291 #define MAYBE_WebContents_Muted DISABLED_WebContents_Muted
292 #else
293 #define MAYBE_WebContents_Muted WebContents_Muted
294 #endif
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,MAYBE_WebContents_Muted)295 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MAYBE_WebContents_Muted) {
296 EXPECT_TRUE(NavigateToURL(shell(),
297 GetTestUrl("media/session", "media-session.html")));
298
299 shell()->web_contents()->SetAudioMuted(true);
300 MediaSession* media_session = MediaSession::Get(shell()->web_contents());
301 ASSERT_NE(nullptr, media_session);
302
303 StartPlaybackAndWait(shell(), "long-video");
304 EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(media_session)
305 ->is_controllable);
306
307 // Unmute the web contents and the player should be created.
308 shell()->web_contents()->SetAudioMuted(false);
309 EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(media_session)
310 ->is_controllable);
311
312 // Now mute it again and the player should be removed.
313 shell()->web_contents()->SetAudioMuted(true);
314 EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(media_session)
315 ->is_controllable);
316 }
317
318 #if !defined(OS_ANDROID)
319 // On Android, System Audio Focus would break this test.
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,MultipleTabsPlayPause)320 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MultipleTabsPlayPause) {
321 Shell* other_shell = CreateBrowser();
322
323 EXPECT_TRUE(NavigateToURL(shell(),
324 GetTestUrl("media/session", "media-session.html")));
325 EXPECT_TRUE(NavigateToURL(other_shell,
326 GetTestUrl("media/session", "media-session.html")));
327
328 MediaSession* media_session = MediaSession::Get(shell()->web_contents());
329 MediaSession* other_media_session =
330 MediaSession::Get(other_shell->web_contents());
331 ASSERT_NE(nullptr, media_session);
332 ASSERT_NE(nullptr, other_media_session);
333
334 StartPlaybackAndWait(shell(), "long-video");
335 StartPlaybackAndWait(other_shell, "long-video");
336
337 media_session->Suspend(MediaSession::SuspendType::kSystem);
338 WaitForStop(shell());
339 EXPECT_FALSE(IsPlaying(shell(), "long-video"));
340 EXPECT_TRUE(IsPlaying(other_shell, "long-video"));
341
342 other_media_session->Suspend(MediaSession::SuspendType::kSystem);
343 WaitForStop(other_shell);
344 EXPECT_FALSE(IsPlaying(shell(), "long-video"));
345 EXPECT_FALSE(IsPlaying(other_shell, "long-video"));
346
347 media_session->Resume(MediaSession::SuspendType::kSystem);
348 WaitForStart(shell());
349 EXPECT_TRUE(IsPlaying(shell(), "long-video"));
350 EXPECT_FALSE(IsPlaying(other_shell, "long-video"));
351
352 other_media_session->Resume(MediaSession::SuspendType::kSystem);
353 WaitForStart(other_shell);
354 EXPECT_TRUE(IsPlaying(shell(), "long-video"));
355 EXPECT_TRUE(IsPlaying(other_shell, "long-video"));
356 }
357 #endif // defined(OS_ANDROID)
358
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,GetMediaImageBitmap)359 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, GetMediaImageBitmap) {
360 ASSERT_TRUE(embedded_test_server()->Start());
361
362 MediaSession* media_session = SetupMediaImageTest();
363 ASSERT_NE(nullptr, media_session);
364
365 media_session::MediaImage image;
366 image.src = embedded_test_server()->GetURL("/media/session/test_image.jpg");
367 image.type = base::ASCIIToUTF16("image/jpeg");
368 image.sizes.push_back(gfx::Size(1, 1));
369
370 MediaImageGetterHelper helper(media_session, CreateTestImageWithSize(1), 0,
371 10);
372 helper.Wait();
373
374 // The test image is a 1x1 test image.
375 EXPECT_EQ(1, helper.bitmap().width());
376 EXPECT_EQ(1, helper.bitmap().height());
377 EXPECT_EQ(kRGBA_8888_SkColorType, helper.bitmap().colorType());
378
379 EXPECT_TRUE(WasURLVisited(GetTestImageURL()));
380 }
381
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,GetMediaImageBitmap_ImageTooSmall)382 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
383 GetMediaImageBitmap_ImageTooSmall) {
384 ASSERT_TRUE(embedded_test_server()->Start());
385
386 MediaSession* media_session = SetupMediaImageTest();
387 ASSERT_NE(nullptr, media_session);
388
389 MediaImageGetterHelper helper(media_session, CreateTestImageWithSize(10), 10,
390 10);
391 helper.Wait();
392
393 // The |image| is too small but we do not know that until after we have
394 // downloaded it. We should still receive a null image though.
395 EXPECT_TRUE(helper.bitmap().isNull());
396 EXPECT_TRUE(WasURLVisited(GetTestImageURL()));
397 }
398
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,GetMediaImageBitmap_ImageTooSmall_BeforeDownload)399 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
400 GetMediaImageBitmap_ImageTooSmall_BeforeDownload) {
401 ASSERT_TRUE(embedded_test_server()->Start());
402
403 MediaSession* media_session = SetupMediaImageTest();
404 ASSERT_NE(nullptr, media_session);
405
406 MediaImageGetterHelper helper(media_session, CreateTestImageWithSize(1), 10,
407 10);
408 helper.Wait();
409
410 // Since |image| is too small but we know this in advance we should not
411 // download it and instead we should receive a null image.
412 EXPECT_TRUE(helper.bitmap().isNull());
413 EXPECT_FALSE(WasURLVisited(GetTestImageURL()));
414 }
415
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,GetMediaImageBitmap_InvalidImage)416 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
417 GetMediaImageBitmap_InvalidImage) {
418 ASSERT_TRUE(embedded_test_server()->Start());
419
420 MediaSession* media_session = SetupMediaImageTest();
421 ASSERT_NE(nullptr, media_session);
422
423 media_session::MediaImage image = CreateTestImageWithSize(1);
424 image.src = embedded_test_server()->GetURL("/blank.jpg");
425
426 MediaImageGetterHelper helper(media_session, image, 0, 10);
427 helper.Wait();
428
429 // Since |image| is not an image that is associated with the test page we
430 // should not download it and instead we should receive a null image.
431 EXPECT_TRUE(helper.bitmap().isNull());
432 EXPECT_FALSE(WasURLVisited(image.src));
433 }
434
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithBackForwardCache,DoNotCacheIfMediaSessionExists)435 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithBackForwardCache,
436 DoNotCacheIfMediaSessionExists) {
437 ASSERT_TRUE(embedded_test_server()->Start());
438
439 // 1) Navigate to a page containing media.
440 EXPECT_TRUE(NavigateToURL(shell(),
441 GetTestUrl("media/session", "media-session.html")));
442
443 RenderFrameHost* rfh_a = shell()->web_contents()->GetMainFrame();
444 RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
445
446 // 2) Navigate away.
447 EXPECT_TRUE(NavigateToURL(
448 shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
449
450 // The page should not have been cached in the back forward cache.
451 delete_observer_rfh_a.WaitUntilDeleted();
452 }
453
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithBackForwardCache,CachesPageWithoutMedia)454 IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithBackForwardCache,
455 CachesPageWithoutMedia) {
456 ASSERT_TRUE(embedded_test_server()->Start());
457
458 // 1) Navigate to a page not containing any media.
459 EXPECT_TRUE(NavigateToURL(
460 shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
461
462 RenderFrameHostImpl* rfh_a = static_cast<RenderFrameHostImpl*>(
463 shell()->web_contents()->GetMainFrame());
464 RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
465
466 // 2) Navigate away.
467 EXPECT_TRUE(NavigateToURL(
468 shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
469
470 // The page should be cached in the back forward cache.
471 EXPECT_FALSE(delete_observer_rfh_a.deleted());
472 EXPECT_TRUE(rfh_a->IsInBackForwardCache());
473 }
474 } // namespace content
475