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