blob: 29be7781104c2742771fe6dcefb660329ac6d042 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/platform_thread_win.h"
#include "base/unguessable_token.h"
#include "content/browser/browser_main_loop.h"
#include "content/browser/media/media_keys_listener_manager_impl.h"
#include "content/browser/media/web_app_system_media_controls.h"
#include "content/browser/media/web_app_system_media_controls_manager.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test.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/shell/browser/shell.h"
#include "media/base/media_switches.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
// This test suite tests playing media in a content window and verifies control
// via system media controls controls the expected window. As instanced system
// media controls is developed under kWebAppSystemMediaControlsWin.
// Currently, this test suite only runs on windows.
class WebAppSystemMediaControlsBrowserTest
: public ContentBrowserTest,
public WebAppSystemMediaControlsManagerObserver,
public MediaKeysListenerManagerImplTestObserver {
public:
WebAppSystemMediaControlsBrowserTest() = default;
WebAppSystemMediaControlsBrowserTest(
const WebAppSystemMediaControlsBrowserTest&) = delete;
WebAppSystemMediaControlsBrowserTest& operator=(
const WebAppSystemMediaControlsBrowserTest&) = delete;
~WebAppSystemMediaControlsBrowserTest() override = default;
void SetUpOnMainThread() override {
// Start an HTTPS server that will serve files in from "content/test/data".
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server_->Start());
// Also start listening to events from a few different classes.
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
media_keys_listener_manager_impl->SetObserverForTesting(this);
WebAppSystemMediaControlsManager* web_app_system_media_controls_manager =
media_keys_listener_manager_impl->web_app_system_media_controls_manager_
.get();
web_app_system_media_controls_manager->SetObserverForTesting(this);
// Tests may want to utilize this runloop to detect when browser has been
// bookkeeping added. We need to create this runloop early enough so that
// the runloop always wins the race between the waiter asking to "wait for
// browser added" and the browser actually being added.
browser_added_run_loop_.emplace();
// Do a similar thing for the web app added run loop.
web_app_added_run_loop_.emplace();
// And finally a similar thing for the watching media key run loop.
start_watching_media_key_run_loop_.emplace();
}
net::EmbeddedTestServer* https_server() { return https_server_.get(); }
void StartPlaybackAndWait(Shell* shell, const std::string& id) {
shell->web_contents()->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16(
JsReplace("document.getElementById($1).play();", id)),
base::NullCallback());
WaitForStart(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) {
return EvalJs(shell->web_contents(),
JsReplace("!document.getElementById($1).paused;", id))
.ExtractBool();
}
WebAppSystemMediaControlsManager* GetWebAppSystemMediaControlsManager() {
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
return media_keys_listener_manager_impl
->web_app_system_media_controls_manager_.get();
}
system_media_controls::SystemMediaControls* GetBrowserSystemMediaControls() {
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
return media_keys_listener_manager_impl->browser_system_media_controls_
.get();
}
system_media_controls::SystemMediaControls* GetSystemMediaControlsForWebApp(
base::UnguessableToken request_id) {
WebAppSystemMediaControls* web_app_system_media_controls =
GetWebAppSystemMediaControlsManager()->GetControlsForRequestId(
request_id);
EXPECT_NE(web_app_system_media_controls, nullptr);
system_media_controls::SystemMediaControls* system_media_controls =
web_app_system_media_controls->GetSystemMediaControls();
EXPECT_NE(system_media_controls, nullptr);
return system_media_controls;
}
// This method asks the WebAppSystemMediaControlsManager to just assume
// requests that come in come from a web app.
void SetAlwaysAssumeWebAppForTesting() {
GetWebAppSystemMediaControlsManager()->always_assume_web_app_for_testing_ =
true;
}
// This mechanism allows us to wait for the browser to be added to
// WebAppSystemMediaControls bookkeeping.
void OnBrowserAdded() override { browser_added_run_loop_->Quit(); }
void WaitForBrowserAdded() {
browser_added_run_loop_->Run();
// Reset the runloop for the next use.
browser_added_run_loop_.emplace();
}
// This mechanism allows us to wait for a web app to be added to the
// WebAppSystemMediaControls bookkeeping.
void OnWebAppAdded(base::UnguessableToken request_id) override {
last_web_app_request_id_ = request_id;
web_app_added_run_loop_->Quit();
}
base::UnguessableToken WaitForWebAppAdded() {
web_app_added_run_loop_->Run();
EXPECT_FALSE(last_web_app_request_id_.is_empty());
base::UnguessableToken cached_request_id = last_web_app_request_id_;
last_web_app_request_id_ = base::UnguessableToken::Null();
// Reset the runloop for the next use.
web_app_added_run_loop_.emplace();
return cached_request_id;
}
// This mechanism allows us to wait for MediaKeysListenerImpl to be ready
// to listen to keys.
void OnStartWatchingMediaKey(bool is_pwa) override {
start_watching_media_key_run_loop_->Quit();
last_watch_was_for_pwa_ = is_pwa;
}
// This function returns whether the last "watching key" event was for a PWA
// or not.
bool WaitForStartWatchingMediaKey() {
start_watching_media_key_run_loop_->Run();
EXPECT_TRUE(
last_watch_was_for_pwa_); // Check the value got set, optional resolves
// to true if the value got set.
bool cached_last_watch_was_for_pwa = last_watch_was_for_pwa_.value();
last_watch_was_for_pwa_ = std::nullopt;
// Reset the runloop for the next use.
start_watching_media_key_run_loop_.emplace();
return cached_last_watch_was_for_pwa;
}
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
switches::kAutoplayPolicy,
switches::autoplay::kNoUserGestureRequiredPolicy);
feature_list_.InitAndEnableFeature(features::kWebAppSystemMediaControlsWin);
ContentBrowserTest::SetUpCommandLine(command_line);
}
private:
std::optional<base::RunLoop> web_app_added_run_loop_;
base::UnguessableToken last_web_app_request_id_;
std::optional<base::RunLoop> browser_added_run_loop_;
std::optional<base::RunLoop> start_watching_media_key_run_loop_;
std::optional<bool> last_watch_was_for_pwa_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(WebAppSystemMediaControlsBrowserTest,
SimpleOneBrowserTest) {
GURL http_url(https_server()->GetURL("/media/session/media-session.html"));
EXPECT_TRUE(NavigateToURL(shell(), http_url));
// Run javascript to play the video, and wait for it to begin playing.
StartPlaybackAndWait(shell(), "long-video-loop");
// Check video is playing.
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
// Wait till the WebAppSystemMediaControlsManager adds the browser.
WaitForBrowserAdded();
// Hit pause via simulating SMTC pause.
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
// Unfortunately, even though we wait for the browser to be added the
// MediaKeysListenerManager can still not have the browser registered
// properly. We have to wait for MediaKeysListenerManager to also add it to
// it's bookkeeping.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_FALSE(is_for_pwa);
// Check video is still playing.
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
media_keys_listener_manager_impl->OnPause(GetBrowserSystemMediaControls());
// Check video is paused.
WaitForStop(shell());
}
IN_PROC_BROWSER_TEST_F(WebAppSystemMediaControlsBrowserTest, ThreeBrowserTest) {
GURL http_url(https_server()->GetURL("/media/session/media-session.html"));
Shell* browser2 = CreateBrowser();
Shell* browser3 = CreateBrowser();
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_TRUE(NavigateToURL(browser2, http_url));
EXPECT_TRUE(NavigateToURL(browser3, http_url));
// Press play and wait for each one to start.
StartPlaybackAndWait(shell(), "long-video-loop");
StartPlaybackAndWait(browser2, "long-video-loop");
StartPlaybackAndWait(browser3, "long-video-loop");
EXPECT_TRUE(IsPlaying(browser3, "long-video-loop"));
EXPECT_TRUE(IsPlaying(browser2, "long-video-loop"));
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
// Now we have 3 things playing at the same time. Browser 3 should have
// control and be shown in SMTC.
// Wait till the WebAppSystemMediaControlsManager adds the browser.
WaitForBrowserAdded();
// Also wait until MediaKeysListenerManagerImpl starts listening for keys.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_FALSE(is_for_pwa);
// Hit pause via simulating SMTC pause.
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
media_keys_listener_manager_impl->OnPause(GetBrowserSystemMediaControls());
// Check audio is paused for browser3.
WaitForStop(browser3);
// The other stuff should be continuing to loop.
EXPECT_TRUE(IsPlaying(browser2, "long-video-loop"));
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
}
IN_PROC_BROWSER_TEST_F(WebAppSystemMediaControlsBrowserTest,
BrowserAndWebAppTest) {
// Navigate two shells to the page.
GURL http_url(https_server()->GetURL("/media/session/media-session.html"));
EXPECT_TRUE(NavigateToURL(shell(), http_url));
Shell* web_app = CreateBrowser();
EXPECT_TRUE(NavigateToURL(web_app, http_url));
EXPECT_TRUE(NavigateToURL(shell(), http_url));
EXPECT_TRUE(NavigateToURL(web_app, http_url));
// Start two playbacks, but set the testing flag so that the second window
// will register as a web app to WebAppSystemMediaControlsManager.
{
StartPlaybackAndWait(shell(), "long-video-loop");
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
// We need to be careful here that this first play is completely done before
// we set the flag to pretend subsequent plays are from apps.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_FALSE(is_for_pwa);
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
}
SetAlwaysAssumeWebAppForTesting();
StartPlaybackAndWait(web_app, "long-video-loop");
base::UnguessableToken request_id = WaitForWebAppAdded();
EXPECT_TRUE(IsPlaying(web_app, "long-video-loop"));
EXPECT_FALSE(request_id.is_empty());
// Now retrieve the SMC and make a call to pause the video.
system_media_controls::SystemMediaControls* system_media_controls =
GetSystemMediaControlsForWebApp(request_id);
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
// Also wait for MediaKeysListenerManagerImpl to also start watching.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_TRUE(is_for_pwa);
media_keys_listener_manager_impl->OnPause(system_media_controls);
// The "web app" should be paused.
WaitForStop(web_app);
// The browser is still playing.
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
// Now start the webapp again.
media_keys_listener_manager_impl->OnPlay(system_media_controls);
WaitForStart(web_app);
// The browser is still playing.
EXPECT_TRUE(IsPlaying(shell(), "long-video-loop"));
}
IN_PROC_BROWSER_TEST_F(WebAppSystemMediaControlsBrowserTest, ThreeWebAppTest) {
// Navigate two shells to the page.
GURL http_url(https_server()->GetURL("/media/session/media-session.html"));
// We're mostly going to ignore this shell() based browser.
Shell* web_app1 = CreateBrowser();
Shell* web_app2 = CreateBrowser();
Shell* web_app3 = CreateBrowser();
EXPECT_TRUE(NavigateToURL(web_app1, http_url));
EXPECT_TRUE(NavigateToURL(web_app2, http_url));
EXPECT_TRUE(NavigateToURL(web_app3, http_url));
// Start all the playbacks.
SetAlwaysAssumeWebAppForTesting();
base::UnguessableToken web_app1_request_id;
base::UnguessableToken web_app2_request_id;
base::UnguessableToken web_app3_request_id;
{
StartPlaybackAndWait(web_app1, "long-video-loop");
web_app1_request_id = WaitForWebAppAdded();
// Also wait until MediaKeysListenerManagerImpl starts listening for keys.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_TRUE(is_for_pwa);
}
{
StartPlaybackAndWait(web_app2, "long-video-loop");
web_app2_request_id = WaitForWebAppAdded();
// Also wait until MediaKeysListenerManagerImpl starts listening for keys.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_TRUE(is_for_pwa);
}
{
StartPlaybackAndWait(web_app3, "long-video-loop");
web_app3_request_id = WaitForWebAppAdded();
// Also wait until MediaKeysListenerManagerImpl starts listening for keys.
bool is_for_pwa = WaitForStartWatchingMediaKey();
EXPECT_TRUE(is_for_pwa);
}
// All request ids should be valid.
EXPECT_NE(web_app1_request_id, base::UnguessableToken::Null());
EXPECT_NE(web_app2_request_id, base::UnguessableToken::Null());
EXPECT_NE(web_app3_request_id, base::UnguessableToken::Null());
system_media_controls::SystemMediaControls* web_app1_system_media_controls =
GetSystemMediaControlsForWebApp(web_app1_request_id);
system_media_controls::SystemMediaControls* web_app2_system_media_controls =
GetSystemMediaControlsForWebApp(web_app2_request_id);
system_media_controls::SystemMediaControls* web_app3_system_media_controls =
GetSystemMediaControlsForWebApp(web_app3_request_id);
MediaKeysListenerManagerImpl* media_keys_listener_manager_impl =
BrowserMainLoop::GetInstance()->media_keys_listener_manager();
media_keys_listener_manager_impl->OnPause(web_app2_system_media_controls);
WaitForStop(web_app2);
// The other stuff should be continuing to loop.
EXPECT_TRUE(IsPlaying(web_app1, "long-video-loop"));
EXPECT_TRUE(IsPlaying(web_app3, "long-video-loop"));
// Pause 3, only 1 remains.
media_keys_listener_manager_impl->OnPause(web_app3_system_media_controls);
WaitForStop(web_app3);
EXPECT_TRUE(IsPlaying(web_app1, "long-video-loop"));
// Pause 1, only 1 remains.
media_keys_listener_manager_impl->OnPause(web_app1_system_media_controls);
WaitForStop(web_app1);
}
} // namespace content