blob: b016a9760fe6b03a5f6fac3b77b96f6ca8577b09 [file] [log] [blame]
// Copyright 2017 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 "content/public/browser/media_session.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/optional.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/lock.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/media_start_stop_observer.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "media/base/media_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/http_request.h"
#include "services/media_session/public/cpp/features.h"
#include "services/media_session/public/cpp/test/audio_focus_test_util.h"
#include "services/media_session/public/cpp/test/mock_media_session.h"
namespace content {
namespace {
const char kMediaSessionImageTestURL[] = "/media/session/image_test_page.html";
const char kMediaSessionImageTestPageVideoElement[] = "video";
const char kMediaSessionTestImagePath[] = "/media/session/test_image.jpg";
class MediaImageGetterHelper {
public:
MediaImageGetterHelper(content::MediaSession* media_session,
const media_session::MediaImage& image,
int min_size,
int desired_size) {
media_session->GetMediaImageBitmap(
image, min_size, desired_size,
base::BindOnce(&MediaImageGetterHelper::OnComplete,
base::Unretained(this)));
}
void Wait() {
if (bitmap_.has_value())
return;
run_loop_.Run();
}
const SkBitmap& bitmap() { return *bitmap_; }
private:
void OnComplete(const SkBitmap& bitmap) {
bitmap_ = bitmap;
run_loop_.Quit();
}
base::RunLoop run_loop_;
base::Optional<SkBitmap> bitmap_;
DISALLOW_COPY_AND_ASSIGN(MediaImageGetterHelper);
};
// Integration tests for content::MediaSession that do not take into
// consideration the implementation details contrary to
// MediaSessionImplBrowserTest.
class MediaSessionBrowserTestBase : public ContentBrowserTest {
public:
MediaSessionBrowserTestBase() {
embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
&MediaSessionBrowserTestBase::OnServerRequest, base::Unretained(this)));
}
void SetUp() override {
ContentBrowserTest::SetUp();
visited_urls_.clear();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
switches::kAutoplayPolicy,
switches::autoplay::kNoUserGestureRequiredPolicy);
}
void StartPlaybackAndWait(Shell* shell, const std::string& id) {
shell->web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16("document.querySelector('#" + id + "').play();"),
base::NullCallback());
WaitForStart(shell);
}
void StopPlaybackAndWait(Shell* shell, const std::string& id) {
shell->web_contents()->GetMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16("document.querySelector('#" + id + "').pause();"),
base::NullCallback());
WaitForStop(shell);
}
void WaitForStart(Shell* shell) {
MediaStartStopObserver observer(shell->web_contents(),
MediaStartStopObserver::Type::kStart);
observer.Wait();
}
void WaitForStop(Shell* shell) {
MediaStartStopObserver observer(shell->web_contents(),
MediaStartStopObserver::Type::kStop);
observer.Wait();
}
bool IsPlaying(Shell* shell, const std::string& id) {
bool result;
EXPECT_TRUE(
ExecuteScriptAndExtractBool(shell->web_contents(),
"window.domAutomationController.send("
"!document.querySelector('#" +
id + "').paused);",
&result));
return result;
}
bool WasURLVisited(const GURL& url) {
base::AutoLock lock(visited_urls_lock_);
return base::Contains(visited_urls_, url);
}
MediaSession* SetupMediaImageTest() {
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(kMediaSessionImageTestURL)));
StartPlaybackAndWait(shell(), kMediaSessionImageTestPageVideoElement);
MediaSession* media_session = MediaSession::Get(shell()->web_contents());
std::vector<media_session::MediaImage> expected_images;
expected_images.push_back(CreateTestImageWithSize(1));
expected_images.push_back(CreateTestImageWithSize(10));
media_session::test::MockMediaSessionMojoObserver observer(*media_session);
observer.WaitForExpectedImagesOfType(
media_session::mojom::MediaSessionImageType::kArtwork, expected_images);
return media_session;
}
media_session::MediaImage CreateTestImageWithSize(int size) const {
media_session::MediaImage image;
image.src = GetTestImageURL();
image.type = base::ASCIIToUTF16("image/jpeg");
image.sizes.push_back(gfx::Size(size, size));
return image;
}
GURL GetTestImageURL() const {
return embedded_test_server()->GetURL(kMediaSessionTestImagePath);
}
private:
void OnServerRequest(const net::test_server::HttpRequest& request) {
// Note this method is called on the EmbeddedTestServer's background thread.
base::AutoLock lock(visited_urls_lock_);
visited_urls_.insert(request.GetURL());
}
// visited_urls_ is accessed both on the main thread and on the
// EmbeddedTestServer's background thread via OnServerRequest(), so it must be
// locked.
base::Lock visited_urls_lock_;
std::set<GURL> visited_urls_;
DISALLOW_COPY_AND_ASSIGN(MediaSessionBrowserTestBase);
};
class MediaSessionBrowserTest : public MediaSessionBrowserTestBase {
public:
MediaSessionBrowserTest() {
feature_list_.InitAndEnableFeature(media::kInternalMediaSession);
}
private:
base::test::ScopedFeatureList feature_list_;
};
class MediaSessionBrowserTestWithoutInternalMediaSession
: public MediaSessionBrowserTestBase {
public:
MediaSessionBrowserTestWithoutInternalMediaSession() {
disabled_feature_list_.InitWithFeatures(
{}, {media::kInternalMediaSession,
media_session::features::kMediaSessionService});
}
private:
base::test::ScopedFeatureList disabled_feature_list_;
};
// A MediaSessionBrowserTest with BackForwardCache enabled.
class MediaSessionBrowserTestWithBackForwardCache
: public MediaSessionBrowserTestBase {
public:
MediaSessionBrowserTestWithBackForwardCache() {
feature_list_.InitWithFeaturesAndParameters(
{{features::kBackForwardCache,
{{"TimeToLiveInBackForwardCacheInSeconds", "3600"}}},
{media::kInternalMediaSession, {}}},
/*disabled_features=*/{});
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
MediaSessionBrowserTestBase::SetUpOnMainThread();
}
private:
base::test::ScopedFeatureList feature_list_;
};
} // anonymous namespace
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithoutInternalMediaSession,
MediaSessionNoOpWhenDisabled) {
EXPECT_TRUE(NavigateToURL(shell(),
GetTestUrl("media/session", "media-session.html")));
MediaSession* media_session = MediaSession::Get(shell()->web_contents());
ASSERT_NE(nullptr, media_session);
StartPlaybackAndWait(shell(), "long-video");
StartPlaybackAndWait(shell(), "long-audio");
media_session->Suspend(MediaSession::SuspendType::kSystem);
StopPlaybackAndWait(shell(), "long-audio");
// At that point, only "long-audio" is paused.
EXPECT_FALSE(IsPlaying(shell(), "long-audio"));
EXPECT_TRUE(IsPlaying(shell(), "long-video"));
}
// Flaky on Linux and Android and Mac. http://crbug.com/1157239,
// http://crbug.com/1157319
#if defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_MAC)
#define MAYBE_SimplePlayPause DISABLED_SimplePlayPause
#else
#define MAYBE_SimplePlayPause SimplePlayPause
#endif
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MAYBE_SimplePlayPause) {
EXPECT_TRUE(NavigateToURL(shell(),
GetTestUrl("media/session", "media-session.html")));
MediaSession* media_session = MediaSession::Get(shell()->web_contents());
ASSERT_NE(nullptr, media_session);
StartPlaybackAndWait(shell(), "long-video");
media_session->Suspend(MediaSession::SuspendType::kSystem);
WaitForStop(shell());
EXPECT_FALSE(IsPlaying(shell(), "long-video"));
media_session->Resume(MediaSession::SuspendType::kSystem);
WaitForStart(shell());
EXPECT_TRUE(IsPlaying(shell(), "long-video"));
}
// Flaky on Linux and Android. http://crbug.com/1157239,
// http://crbug.com/1157319
#if defined(OS_LINUX) || defined(OS_ANDROID)
#define MAYBE_MultiplePlayersPlayPause DISABLED_MultiplePlayersPlayPause
#else
#define MAYBE_MultiplePlayersPlayPause MultiplePlayersPlayPause
#endif
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
MAYBE_MultiplePlayersPlayPause) {
EXPECT_TRUE(NavigateToURL(shell(),
GetTestUrl("media/session", "media-session.html")));
MediaSession* media_session = MediaSession::Get(shell()->web_contents());
ASSERT_NE(nullptr, media_session);
StartPlaybackAndWait(shell(), "long-video");
StartPlaybackAndWait(shell(), "long-audio");
media_session->Suspend(MediaSession::SuspendType::kSystem);
WaitForStop(shell());
EXPECT_FALSE(IsPlaying(shell(), "long-video"));
EXPECT_FALSE(IsPlaying(shell(), "long-audio"));
media_session->Resume(MediaSession::SuspendType::kSystem);
WaitForStart(shell());
EXPECT_TRUE(IsPlaying(shell(), "long-video"));
EXPECT_TRUE(IsPlaying(shell(), "long-audio"));
}
// Flaky on Mac. See https://crbug.com/980663
#if defined(OS_MAC)
#define MAYBE_WebContents_Muted DISABLED_WebContents_Muted
#else
#define MAYBE_WebContents_Muted WebContents_Muted
#endif
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MAYBE_WebContents_Muted) {
EXPECT_TRUE(NavigateToURL(shell(),
GetTestUrl("media/session", "media-session.html")));
shell()->web_contents()->SetAudioMuted(true);
MediaSession* media_session = MediaSession::Get(shell()->web_contents());
ASSERT_NE(nullptr, media_session);
StartPlaybackAndWait(shell(), "long-video");
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(media_session)
->is_controllable);
// Unmute the web contents and the player should be created.
shell()->web_contents()->SetAudioMuted(false);
EXPECT_TRUE(media_session::test::GetMediaSessionInfoSync(media_session)
->is_controllable);
// Now mute it again and the player should be removed.
shell()->web_contents()->SetAudioMuted(true);
EXPECT_FALSE(media_session::test::GetMediaSessionInfoSync(media_session)
->is_controllable);
}
#if !defined(OS_ANDROID)
// On Android, System Audio Focus would break this test.
// Flaky: http://crbug.com/1157263
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
DISABLED_MultipleTabsPlayPause) {
Shell* other_shell = CreateBrowser();
EXPECT_TRUE(NavigateToURL(shell(),
GetTestUrl("media/session", "media-session.html")));
EXPECT_TRUE(NavigateToURL(other_shell,
GetTestUrl("media/session", "media-session.html")));
MediaSession* media_session = MediaSession::Get(shell()->web_contents());
MediaSession* other_media_session =
MediaSession::Get(other_shell->web_contents());
ASSERT_NE(nullptr, media_session);
ASSERT_NE(nullptr, other_media_session);
StartPlaybackAndWait(shell(), "long-video");
StartPlaybackAndWait(other_shell, "long-video");
media_session->Suspend(MediaSession::SuspendType::kSystem);
WaitForStop(shell());
EXPECT_FALSE(IsPlaying(shell(), "long-video"));
EXPECT_TRUE(IsPlaying(other_shell, "long-video"));
other_media_session->Suspend(MediaSession::SuspendType::kSystem);
WaitForStop(other_shell);
EXPECT_FALSE(IsPlaying(shell(), "long-video"));
EXPECT_FALSE(IsPlaying(other_shell, "long-video"));
media_session->Resume(MediaSession::SuspendType::kSystem);
WaitForStart(shell());
EXPECT_TRUE(IsPlaying(shell(), "long-video"));
EXPECT_FALSE(IsPlaying(other_shell, "long-video"));
other_media_session->Resume(MediaSession::SuspendType::kSystem);
WaitForStart(other_shell);
EXPECT_TRUE(IsPlaying(shell(), "long-video"));
EXPECT_TRUE(IsPlaying(other_shell, "long-video"));
}
#endif // defined(OS_ANDROID)
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, GetMediaImageBitmap) {
ASSERT_TRUE(embedded_test_server()->Start());
MediaSession* media_session = SetupMediaImageTest();
ASSERT_NE(nullptr, media_session);
media_session::MediaImage image;
image.src = embedded_test_server()->GetURL("/media/session/test_image.jpg");
image.type = base::ASCIIToUTF16("image/jpeg");
image.sizes.push_back(gfx::Size(1, 1));
MediaImageGetterHelper helper(media_session, CreateTestImageWithSize(1), 0,
10);
helper.Wait();
// The test image is a 1x1 test image.
EXPECT_EQ(1, helper.bitmap().width());
EXPECT_EQ(1, helper.bitmap().height());
EXPECT_EQ(kRGBA_8888_SkColorType, helper.bitmap().colorType());
EXPECT_TRUE(WasURLVisited(GetTestImageURL()));
}
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
GetMediaImageBitmap_ImageTooSmall) {
ASSERT_TRUE(embedded_test_server()->Start());
MediaSession* media_session = SetupMediaImageTest();
ASSERT_NE(nullptr, media_session);
MediaImageGetterHelper helper(media_session, CreateTestImageWithSize(10), 10,
10);
helper.Wait();
// The |image| is too small but we do not know that until after we have
// downloaded it. We should still receive a null image though.
EXPECT_TRUE(helper.bitmap().isNull());
EXPECT_TRUE(WasURLVisited(GetTestImageURL()));
}
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
GetMediaImageBitmap_ImageTooSmall_BeforeDownload) {
ASSERT_TRUE(embedded_test_server()->Start());
MediaSession* media_session = SetupMediaImageTest();
ASSERT_NE(nullptr, media_session);
MediaImageGetterHelper helper(media_session, CreateTestImageWithSize(1), 10,
10);
helper.Wait();
// Since |image| is too small but we know this in advance we should not
// download it and instead we should receive a null image.
EXPECT_TRUE(helper.bitmap().isNull());
EXPECT_FALSE(WasURLVisited(GetTestImageURL()));
}
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest,
GetMediaImageBitmap_InvalidImage) {
ASSERT_TRUE(embedded_test_server()->Start());
MediaSession* media_session = SetupMediaImageTest();
ASSERT_NE(nullptr, media_session);
media_session::MediaImage image = CreateTestImageWithSize(1);
image.src = embedded_test_server()->GetURL("/blank.jpg");
MediaImageGetterHelper helper(media_session, image, 0, 10);
helper.Wait();
// Since |image| is not an image that is associated with the test page we
// should not download it and instead we should receive a null image.
EXPECT_TRUE(helper.bitmap().isNull());
EXPECT_FALSE(WasURLVisited(image.src));
}
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithBackForwardCache,
DoNotCacheIfMediaSessionExists) {
ASSERT_TRUE(embedded_test_server()->Start());
// 1) Navigate to a page containing media.
EXPECT_TRUE(NavigateToURL(shell(),
GetTestUrl("media/session", "media-session.html")));
RenderFrameHost* rfh_a = shell()->web_contents()->GetMainFrame();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
// 2) Navigate away.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
// The page should not have been cached in the back forward cache.
delete_observer_rfh_a.WaitUntilDeleted();
}
IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTestWithBackForwardCache,
CachesPageWithoutMedia) {
ASSERT_TRUE(embedded_test_server()->Start());
// 1) Navigate to a page not containing any media.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("a.com", "/title1.html")));
RenderFrameHostImpl* rfh_a = static_cast<RenderFrameHostImpl*>(
shell()->web_contents()->GetMainFrame());
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
// 2) Navigate away.
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL("b.com", "/title1.html")));
// The page should be cached in the back forward cache.
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
}
} // namespace content