blob: 69309d2d5c241a711194c3ecc75924cbb2987216 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/media_router/media_route_starter.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/media/router/chrome_media_router_factory.h"
#include "chrome/browser/media/router/test/provider_test_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/browser/ui/global_media_controls/test_helper.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/media_router/query_result_manager.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/media_router/browser/logger_impl.h"
#include "components/media_router/browser/media_router_factory.h"
#include "components/media_router/browser/media_sinks_observer.h"
#include "components/media_router/browser/presentation/web_contents_presentation_manager.h"
#include "components/media_router/browser/test/mock_media_router.h"
#include "components/media_router/browser/test/test_helper.h"
#include "components/media_router/common/discovery/media_sink_internal.h"
#include "components/media_router/common/media_sink.h"
#include "components/media_router/common/route_request_result.h"
#include "components/media_router/common/test/test_helper.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/sessions/core/session_id.h"
#include "content/public/browser/presentation_request.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#include "ui/base/cocoa/permissions_utils.h"
#endif
using testing::_;
using testing::Invoke;
using testing::NiceMock;
using testing::Return;
namespace media_router {
namespace {
constexpr char kPresentationId[] = "session-67890";
constexpr char kLoggerComponent[] = "MediaRouteStarterTests";
const CastModeSet kDefaultModes = {MediaCastMode::PRESENTATION,
MediaCastMode::TAB_MIRROR,
MediaCastMode::DESKTOP_MIRROR};
const CastModeSet kMirroringOnly = {MediaCastMode::TAB_MIRROR,
MediaCastMode::DESKTOP_MIRROR};
const CastModeSet kPresentationOnly = {MediaCastMode::PRESENTATION};
const CastModeSet kDestkopMirrorOnly = {MediaCastMode::DESKTOP_MIRROR};
const CastModeSet kRemotePlaybackOnly = {MediaCastMode::REMOTE_PLAYBACK};
constexpr char kDefaultPresentationUrl[] = "https://defaultpresentation.com/";
constexpr char kDefaultOriginUrl[] = "https://default.fakeurl/";
constexpr char kStartPresentationUrl[] = "https://startpresentrequest.com/";
constexpr char kStartOriginUrl[] = "https://start.fakeurl/";
constexpr char kRemotePlaybackUrl[] =
"remote-playback:media-element?source=encoded_data&video_codec=vp8&audio_"
"codec=mp3";
class MockPresentationRequestSourceObserver
: public PresentationRequestSourceObserver {
public:
MockPresentationRequestSourceObserver() = default;
explicit MockPresentationRequestSourceObserver(MediaRouteStarter* starter)
: starter_(starter) {
starter_->AddPresentationRequestSourceObserver(this);
}
~MockPresentationRequestSourceObserver() override {
if (starter_)
starter_->RemovePresentationRequestSourceObserver(this);
}
MOCK_METHOD(void, OnSourceUpdated, (std::u16string&));
private:
raw_ptr<MediaRouteStarter> starter_ = nullptr;
};
// For demonstrating that presentation mode callbacks are made from the MRS
// d'tor
class PresentationRequestCallbacks {
public:
PresentationRequestCallbacks() = default;
explicit PresentationRequestCallbacks(
const blink::mojom::PresentationError& expected_error)
: expected_error_(expected_error) {}
~PresentationRequestCallbacks() { EXPECT_TRUE(called_); }
void Success(const blink::mojom::PresentationInfo&,
mojom::RoutePresentationConnectionPtr,
const MediaRoute&) {
NOTREACHED();
}
void Error(const blink::mojom::PresentationError& error) {
EXPECT_EQ(expected_error_.error_type, error.error_type);
EXPECT_EQ(expected_error_.message, error.message);
called_ = true;
}
private:
blink::mojom::PresentationError expected_error_;
bool called_ = false;
};
} // namespace
class MediaRouteStarterTest : public ChromeRenderViewHostTestHarness {
public:
// For demonstrating that presentation mode casting callbacks are called.
MOCK_METHOD(void,
RequestSuccess,
(const blink::mojom::PresentationInfo&,
mojom::RoutePresentationConnectionPtr,
const MediaRoute&));
MOCK_METHOD(void,
RequestError,
(const blink::mojom::PresentationError& error));
protected:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
profile_manager_ = std::make_unique<TestingProfileManager>(
TestingBrowserProcess::GetGlobal());
ASSERT_TRUE(profile_manager_->SetUp());
presentation_manager_ =
std::make_unique<NiceMock<MockWebContentsPresentationManager>>();
WebContentsPresentationManager::SetTestInstance(presentation_manager());
SetMediaRouterFactory();
logger_ = std::make_unique<LoggerImpl>();
CreateSessionServiceTabHelper(web_contents());
cast_sink_ = CreateCastSink(1);
dial_sink_ = CreateDialSink(1);
CreateStarterForDefaultModes();
}
void TearDown() override {
clear_screen_capture_allowed_for_testing();
DestroyMediaRouteStarter();
profile_manager_->DeleteAllTestingProfiles();
profile_manager_.reset();
WebContentsPresentationManager::SetTestInstance(nullptr);
ChromeRenderViewHostTestHarness::TearDown();
}
virtual void SetMediaRouterFactory() {
ChromeMediaRouterFactory::GetInstance()->SetTestingFactory(
GetBrowserContext(), base::BindRepeating(&MockMediaRouter::Create));
Profile* default_profile =
#if BUILDFLAG(IS_CHROMEOS)
ProfileManager::GetActiveUserProfile();
#else
ProfileManager::GetLastUsedProfile();
#endif // BUILDFLAG(IS_CHROMEOS)
ChromeMediaRouterFactory::GetInstance()->SetTestingFactory(
default_profile, base::BindRepeating(&MockMediaRouter::Create));
}
void CreateStarterForDefaultModes() {
CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents()));
}
void CreateStarter(MediaRouterUIParameters params) {
starter_ = std::make_unique<MediaRouteStarter>(std::move(params));
starter_->SetLoggerComponent(kLoggerComponent);
ON_CALL(*media_router(), GetLogger()).WillByDefault(Return(logger_.get()));
// Store sink observers so that they can be notified in tests.
ON_CALL(*media_router(), RegisterMediaSinksObserver(_))
.WillByDefault([this](MediaSinksObserver* observer) {
media_sinks_observers_.push_back(observer);
return true;
});
// Remove sink observers as appropriate (destructing handlers will cause
// this to occur).
ON_CALL(*media_router(), UnregisterMediaSinksObserver(_))
.WillByDefault([this](MediaSinksObserver* observer) {
auto it = base::ranges::find(media_sinks_observers_, observer);
if (it != media_sinks_observers_.end()) {
media_sinks_observers_.erase(it);
}
});
// Handler so MockMediaRouter will respond to requests to create a route.
// Will construct a RouteRequestResult based on the set result code and
// then call the handler's callback.
ON_CALL(*media_router(), CreateRouteInternal(_, _, _, _, _, _))
.WillByDefault([this](const MediaSource::Id& source_id,
const MediaSink::Id& sink_id,
const url::Origin& origin,
content::WebContents* web_contents,
MediaRouteResponseCallback& callback,
base::TimeDelta timeout) {
// This indicates the test did not properly set the expected result
EXPECT_NE(mojom::RouteRequestResultCode::UNKNOWN_ERROR, result_code_);
std::unique_ptr<RouteRequestResult> result;
if (result_code_ == mojom::RouteRequestResultCode::OK) {
result = GetSuccessResult(source_id, sink_id);
} else {
result = GetErrorResult(result_code_);
}
std::move(callback).Run(nullptr, *result);
});
}
MediaRouteStarter* media_route_starter() { return starter_.get(); }
TestingProfileManager* profile_manager() { return profile_manager_.get(); }
MockMediaRouter* media_router() {
return static_cast<MockMediaRouter*>(
media_route_starter()->GetMediaRouter());
}
LoggerImpl* logger() { return logger_.get(); }
const std::vector<raw_ptr<MediaSinksObserver, VectorExperimental>>
media_sink_observers() {
return media_sinks_observers_;
}
MockWebContentsPresentationManager* presentation_manager() {
return presentation_manager_.get();
}
QueryResultManager* query_result_manager() {
return media_route_starter()->GetQueryResultManager();
}
const RouteRequestResult* route_request_result() {
return route_request_result_.get();
}
const MediaSinkInternal& cast_sink() { return cast_sink_; }
const MediaSinkInternal& dial_sink() { return dial_sink_; }
const content::PresentationRequest& default_presentation_request() {
return default_presentation_request_;
}
const content::PresentationRequest& start_presentation_request() {
return start_presentation_request_;
}
void set_expected_cast_result(mojom::RouteRequestResultCode code) {
result_code_ = code;
}
std::unique_ptr<StartPresentationContext> CreateStartPresentationContext(
const content::PresentationRequest& presentation_request) {
return std::make_unique<StartPresentationContext>(
presentation_request,
base::BindOnce(&MediaRouteStarterTest::RequestSuccess,
base::Unretained(this)),
base::BindOnce(&MediaRouteStarterTest::RequestError,
base::Unretained(this)));
}
// The caller must hold on to PresentationRequestCallbacks returned so that
// a callback can later be called on it.
std::unique_ptr<PresentationRequestCallbacks> ExpectPresentationError(
const content::PresentationRequest& presentation_request,
blink::mojom::PresentationErrorType error_type,
const std::string& error_message) {
blink::mojom::PresentationError expected_error(error_type, error_message);
auto request_callbacks =
std::make_unique<PresentationRequestCallbacks>(expected_error);
auto start_presentation_context =
std::make_unique<StartPresentationContext>(
presentation_request,
base::BindOnce(&PresentationRequestCallbacks::Success,
base::Unretained(request_callbacks.get())),
base::BindOnce(&PresentationRequestCallbacks::Error,
base::Unretained(request_callbacks.get())));
CreateStarter(
MediaRouterUIParameters(kPresentationOnly, web_contents(),
std::move(start_presentation_context)));
return request_callbacks;
}
void UpdateSinks(const std::vector<MediaSink>& sinks,
const std::vector<url::Origin>& origins) {
for (MediaSinksObserver* sinks_observer : media_sink_observers()) {
sinks_observer->OnSinksUpdated(sinks, origins);
}
}
std::string GetLogEntry(const std::string& logs_json,
const std::string& attribute) {
base::Value logs = base::JSONReader::Read(logs_json).value();
return *logs.GetList()[0].GetDict().FindString(attribute);
}
void DestroyMediaRouteStarter() { starter_.reset(); }
std::unique_ptr<RouteRequestResult> GetSuccessResult(
const MediaSource::Id& source_id,
const MediaSink::Id& sink_id) {
MediaSource source(source_id);
MediaRoute route;
route.set_media_route_id(source_id + "->" + sink_id);
route.set_media_source(source);
route.set_media_sink_id(sink_id);
return RouteRequestResult::FromSuccess(
route, IsValidPresentationUrl(source.url()) ? kPresentationId : "");
}
std::unique_ptr<RouteRequestResult> GetSuccessResult(
const content::PresentationRequest& request,
const MediaSink::Id& sink_id) {
auto source =
MediaSource::ForPresentationUrl(*(request.presentation_urls.begin()))
.id();
return GetSuccessResult(source, sink_id);
}
std::unique_ptr<RouteRequestResult> GetErrorResult(
mojom::RouteRequestResultCode result_code) {
return RouteRequestResult::FromError("unit test error", result_code);
}
void StartMirroring(const MediaSinkInternal& sink, MediaCastMode cast_mode) {
EXPECT_NE(MediaCastMode::PRESENTATION, cast_mode);
// Add a sink
UpdateSinks({sink.sink()}, std::vector<url::Origin>());
auto params =
media_route_starter()->CreateRouteParameters(sink.id(), cast_mode);
EXPECT_TRUE(params);
if (cast_mode == MediaCastMode::DESKTOP_MIRROR)
set_screen_capture_allowed_for_testing(true);
StartRoute(std::move(params));
}
void StartPresentation(const MediaSinkInternal& sink,
const content::PresentationRequest& request) {
// Add a presentation compatible sink
UpdateSinks({sink.sink()}, {request.frame_origin});
auto params = media_route_starter()->CreateRouteParameters(
sink.id(), MediaCastMode::PRESENTATION);
EXPECT_TRUE(params);
StartRoute(std::move(params));
}
void StartRemotePlayback(const MediaSinkInternal& sink) {
UpdateSinks({sink.sink()}, std::vector<url::Origin>());
auto params = media_route_starter()->CreateRouteParameters(
sink.id(), MediaCastMode::REMOTE_PLAYBACK);
EXPECT_TRUE(params);
StartRoute(std::move(params));
}
content::PresentationRequest CreatePresentationRequest(
const std::string& presentation_url,
const std::string& origin_url) {
content::PresentationRequest presentation_request(
{0, 0}, {GURL(presentation_url)},
url::Origin::Create(GURL(origin_url)));
return presentation_request;
}
private:
void StartRoute(std::unique_ptr<RouteParameters> params) {
// To demonstrate that MediaRouteResultCallbacks are called
params->route_result_callbacks.emplace_back(
base::BindOnce(&MediaRouteStarterTest::HandleMediaRouteResponse,
base::Unretained(this)));
EXPECT_CALL(*media_router(),
CreateRouteInternal(params->source_id, params->request->sink_id,
params->origin, web_contents(), _,
params->timeout));
media_route_starter()->StartRoute(std::move(params));
}
// For demonstrating that the MediaRouteResultCallbacks are called.
void HandleMediaRouteResponse(const RouteRequestResult& result) {
// Store the response so tests can examine it.
if (result.result_code() == mojom::RouteRequestResultCode::OK) {
auto route = std::make_unique<MediaRoute>(*result.route());
route_request_result_ = std::make_unique<RouteRequestResult>(
std::move(route), result.presentation_id(), result.error(),
result.result_code());
} else {
route_request_result_ =
RouteRequestResult::FromError(result.error(), result.result_code());
}
}
std::unique_ptr<MediaRouteStarter> starter_;
std::unique_ptr<TestingProfileManager> profile_manager_;
std::unique_ptr<LoggerImpl> logger_;
std::vector<raw_ptr<MediaSinksObserver, VectorExperimental>>
media_sinks_observers_;
std::unique_ptr<MockWebContentsPresentationManager> presentation_manager_;
MediaSinkInternal cast_sink_;
MediaSinkInternal dial_sink_;
content::PresentationRequest default_presentation_request_ =
CreatePresentationRequest(kDefaultPresentationUrl, kDefaultOriginUrl);
content::PresentationRequest start_presentation_request_ =
CreatePresentationRequest(kStartPresentationUrl, kStartOriginUrl);
mojom::RouteRequestResultCode result_code_ =
mojom::RouteRequestResultCode::UNKNOWN_ERROR;
std::unique_ptr<RouteRequestResult> route_request_result_;
};
// Demonstrates that when initialized with webcontents but no presentation
// source the supported modes are mirroring only.
TEST_F(MediaRouteStarterTest, Defaults_NoPresentation) {
CreateStarterForDefaultModes();
EXPECT_EQ(kMirroringOnly, query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that when initialized with webcontents that has a default
// presentation that presentation mode is supported.
TEST_F(MediaRouteStarterTest, Defaults_WebContentPresentation) {
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
CreateStarterForDefaultModes();
EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that if caller doesn't request mirroring that it is not
// available.
TEST_F(MediaRouteStarterTest, Defaults_WebContentPresentationOnly) {
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
CreateStarter(MediaRouterUIParameters(kPresentationOnly, web_contents()));
EXPECT_EQ(kPresentationOnly, query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that when initialized with a presentation request that
// presentation mode is supported - even if the web contents has no default
// presentation.
TEST_F(MediaRouteStarterTest, Defaults_StartPresentationContext) {
auto start_presentation_context =
CreateStartPresentationContext(start_presentation_request());
CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
std::move(start_presentation_context)));
EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());
// This is to deal with the error callback in the d'tor that's not part of
// this test. See the Dtor_* tests below where this case is actually
// validated.
EXPECT_CALL(*this, RequestError(_));
}
// Demonstrates that when initialized with Remote Playback mode
TEST_F(MediaRouteStarterTest, Defaults_RemotePlayback) {
CreateStarter(MediaRouterUIParameters(kRemotePlaybackOnly, web_contents(),
nullptr, media::VideoCodec::kVP8,
media::AudioCodec::kOpus));
EXPECT_EQ(kRemotePlaybackOnly,
query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that when initialized with no webcontent or presentation source
// the supported modes are desktop mirroring only.
TEST_F(MediaRouteStarterTest, Defaults_NoWebContent) {
CreateStarter(MediaRouterUIParameters(kDefaultModes, nullptr));
EXPECT_EQ(kDestkopMirrorOnly,
query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that when MediaRouteStarter is notified that the presentation
// request source has changed, that it alerts observers with the name of that
// presentation request source.
TEST_F(MediaRouteStarterTest, OnPresentationRequestSourceUpdated) {
CreateStarterForDefaultModes();
EXPECT_EQ(kMirroringOnly, query_result_manager()->GetSupportedCastModes());
MockPresentationRequestSourceObserver observer(media_route_starter());
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
std::u16string expected_name(u"default.fakeurl");
EXPECT_CALL(observer, OnSourceUpdated(expected_name));
// Simulate the notification that the default presentation has changed.
media_route_starter()->OnDefaultPresentationChanged(
&presentation_manager()->GetDefaultPresentationRequest());
// Now that a default presentation has been added the available modes should
// include presentation.
EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that when MediaRouteStarter is notified that there is no
// presentation request source that it alerts observers that the name of the
// presentation source is empty.
TEST_F(MediaRouteStarterTest, OnPresentationRequestSourceRemoved) {
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
CreateStarterForDefaultModes();
EXPECT_EQ(kDefaultModes, query_result_manager()->GetSupportedCastModes());
MockPresentationRequestSourceObserver observer(media_route_starter());
// Simulate the notification that the default presentation has been removed.
std::u16string expected_name(u"");
EXPECT_CALL(observer, OnSourceUpdated(expected_name));
media_route_starter()->OnDefaultPresentationChanged(nullptr);
// Now that a default presentation has been added the available modes should
// include presentation.
EXPECT_EQ(kMirroringOnly, query_result_manager()->GetSupportedCastModes());
}
// Demonstrates that if MediaRouteStarter is destroyed without an attempt to
// create a route with the presentation source, that the expected error is
// reported to the presentation source.
TEST_F(MediaRouteStarterTest, Dtor_NotFoundError_NoSinks) {
auto request_callbacks = ExpectPresentationError(
default_presentation_request(),
blink::mojom::PresentationErrorType::NO_AVAILABLE_SCREENS,
"No screens found.");
// Destroying the starter should return the expected error from above to the
// error callback.
DestroyMediaRouteStarter();
}
// Same as above, but demonstrates the same error reporting even if sinks exist,
// so long as none of the sinks were compatible with the presentation source.
TEST_F(MediaRouteStarterTest, Dtor_NotFoundError_NoCompatibleSinks) {
auto request_callbacks = ExpectPresentationError(
default_presentation_request(),
blink::mojom::PresentationErrorType::NO_AVAILABLE_SCREENS,
"No screens found.");
// Send a sink to the UI that is compatible with sources other than the
// presentation url to cause a NotFoundError.
std::vector<MediaSink> sinks = {dial_sink().sink()};
auto presentation_source = MediaSource::ForPresentationUrl(
default_presentation_request().presentation_urls[0]);
for (MediaSinksObserver* sinks_observer : media_sink_observers()) {
if (!(sinks_observer->source() == presentation_source)) {
sinks_observer->OnSinksUpdated(sinks, {});
}
}
// Destroying the starter should return the expected error from above to the
// error callback.
DestroyMediaRouteStarter();
}
// Same as above, but demonstrates that if a compatible sink was present, then
// the error that is reported indicates that the request was cancelled.
TEST_F(MediaRouteStarterTest, Dtor_AbortError) {
auto request_callbacks = ExpectPresentationError(
default_presentation_request(),
blink::mojom::PresentationErrorType::PRESENTATION_REQUEST_CANCELLED,
"Dialog closed.");
// Send a sink to the UI that is compatible with the presentation url to avoid
// a NotFoundError.
std::vector<MediaSink> sinks = {dial_sink().sink()};
auto presentation_source = MediaSource::ForPresentationUrl(
default_presentation_request().presentation_urls[0]);
for (MediaSinksObserver* sinks_observer : media_sink_observers()) {
if (sinks_observer->source() == presentation_source) {
sinks_observer->OnSinksUpdated(sinks, {});
}
}
// Destroying the starter should return the expected error from above to the
// error callback.
DestroyMediaRouteStarter();
}
// Demonstrates that if there are no sources available for the desired mode
// CreateRouteParameters returns nothing.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_NoValidSource) {
// No presentation available
CreateStarter(MediaRouterUIParameters(kMirroringOnly, web_contents()));
// Add a sink
UpdateSinks({cast_sink().sink()}, std::vector<url::Origin>());
auto params = media_route_starter()->CreateRouteParameters(
cast_sink().id(), MediaCastMode::PRESENTATION);
EXPECT_FALSE(params);
}
// Demonstrates that when desktop mirroring is available and requested that the
// RouteParameters are properly filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_DesktopMirroring) {
CreateStarterForDefaultModes();
// Add a sink
UpdateSinks({cast_sink().sink()}, std::vector<url::Origin>());
auto params = media_route_starter()->CreateRouteParameters(
cast_sink().id(), MediaCastMode::DESKTOP_MIRROR);
EXPECT_EQ(MediaCastMode::DESKTOP_MIRROR, params->cast_mode);
EXPECT_EQ(MediaSource::ForUnchosenDesktop().id(), params->source_id);
EXPECT_EQ(cast_sink().id(), params->request->sink_id);
EXPECT_EQ(GURL(), params->origin.GetURL());
// route_result_callbacks should only be filled in by caller
EXPECT_EQ(0ul, params->route_result_callbacks.size());
EXPECT_EQ(base::Seconds(120), params->timeout);
}
// Demonstrates that when tab mirroring is available and requested that the
// RouteParameters are properly filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_TabMirroring) {
SessionID::id_type tab_id =
sessions::SessionTabHelper::IdForTab(web_contents()).id();
CreateStarterForDefaultModes();
// Add a sink
UpdateSinks({cast_sink().sink()}, std::vector<url::Origin>());
auto params = media_route_starter()->CreateRouteParameters(
cast_sink().id(), MediaCastMode::TAB_MIRROR);
EXPECT_EQ(MediaCastMode::TAB_MIRROR, params->cast_mode);
EXPECT_EQ(MediaSource::ForTab(tab_id).id(), params->source_id);
EXPECT_EQ(cast_sink().id(), params->request->sink_id);
EXPECT_EQ(GURL(), params->origin.GetURL());
// route_result_callbacks should only be filled in by caller
EXPECT_EQ(0ul, params->route_result_callbacks.size());
EXPECT_EQ(base::Seconds(60), params->timeout);
}
// Demonstrates that when presentation mode is available for the default
// presentation and requested that the RouteParameters are properly filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_WebContentPresentation) {
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
CreateStarterForDefaultModes();
// Add a presentation compatible sink
UpdateSinks({cast_sink().sink()},
{default_presentation_request().frame_origin});
auto params = media_route_starter()->CreateRouteParameters(
cast_sink().id(), MediaCastMode::PRESENTATION);
EXPECT_EQ(MediaCastMode::PRESENTATION, params->cast_mode);
EXPECT_EQ(MediaSource::ForPresentationUrl(
*(default_presentation_request()).presentation_urls.begin())
.id(),
params->source_id);
EXPECT_EQ(cast_sink().id(), params->request->sink_id);
EXPECT_EQ(default_presentation_request().frame_origin, params->origin);
// route_result_callbacks should only be filled in by caller
EXPECT_EQ(0ul, params->route_result_callbacks.size());
EXPECT_EQ(base::Seconds(20), params->timeout);
}
// Demonstrates that when presentation mode is requested and a start
// presentation context is available that the RouteParameters are correctly
// filled out.
TEST_F(MediaRouteStarterTest, CreateRouteParameters_StartPresentationContext) {
auto start_presentation_context =
CreateStartPresentationContext(start_presentation_request());
CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
std::move(start_presentation_context)));
// Add a presentation compatible sink
UpdateSinks({cast_sink().sink()},
{start_presentation_request().frame_origin});
auto params = media_route_starter()->CreateRouteParameters(
cast_sink().id(), MediaCastMode::PRESENTATION);
EXPECT_EQ(MediaCastMode::PRESENTATION, params->cast_mode);
EXPECT_EQ(MediaSource::ForPresentationUrl(
*(start_presentation_request().presentation_urls.begin()))
.id(),
params->source_id);
EXPECT_EQ(cast_sink().id(), params->request->sink_id);
EXPECT_EQ(start_presentation_request().frame_origin, params->origin);
// route_result_callbacks should only be filled in by caller
EXPECT_EQ(0ul, params->route_result_callbacks.size());
EXPECT_EQ(base::Seconds(20), params->timeout);
// This is to deal with the error callback in the d'tor that's not part of
// this test. See the Dtor_* tests below where this case is actually
// validated.
EXPECT_CALL(*this, RequestError(_));
}
// Demonstrates that desktop mirroring routes are created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_DesktopMirroring) {
CreateStarterForDefaultModes();
set_expected_cast_result(mojom::RouteRequestResultCode::OK);
StartMirroring(cast_sink(), MediaCastMode::DESKTOP_MIRROR);
EXPECT_EQ(mojom::RouteRequestResultCode::OK,
route_request_result()->result_code());
MediaSource expected_source = MediaSource::ForUnchosenDesktop();
EXPECT_EQ(expected_source, route_request_result()->route()->media_source());
}
// Demonstrates that failures to create desktop mirroring routes are propagated.
TEST_F(MediaRouteStarterTest, StartRoute_DesktopMirroringError) {
CreateStarterForDefaultModes();
set_expected_cast_result(mojom::RouteRequestResultCode::ROUTE_NOT_FOUND);
StartMirroring(cast_sink(), MediaCastMode::DESKTOP_MIRROR);
EXPECT_EQ(mojom::RouteRequestResultCode::ROUTE_NOT_FOUND,
route_request_result()->result_code());
}
// Demonstrates that tab mirroring routes are created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_TabMirroring) {
CreateStarterForDefaultModes();
set_expected_cast_result(mojom::RouteRequestResultCode::OK);
StartMirroring(cast_sink(), MediaCastMode::TAB_MIRROR);
EXPECT_EQ(mojom::RouteRequestResultCode::OK,
route_request_result()->result_code());
MediaSource expected_source = MediaSource::ForTab(
sessions::SessionTabHelper::IdForTab(web_contents()).id());
EXPECT_EQ(expected_source, route_request_result()->route()->media_source());
}
// Demonstrates that failures to create tab mirroring routes are propagated.
TEST_F(MediaRouteStarterTest, StartRoute_TabMirroringError) {
CreateStarterForDefaultModes();
set_expected_cast_result(mojom::RouteRequestResultCode::INVALID_ORIGIN);
StartMirroring(cast_sink(), MediaCastMode::DESKTOP_MIRROR);
EXPECT_EQ(mojom::RouteRequestResultCode::INVALID_ORIGIN,
route_request_result()->result_code());
}
// Demonstrates that presentations routes from web content are created
// correctly.
TEST_F(MediaRouteStarterTest, StartRoute_WebContentPresentation) {
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
CreateStarterForDefaultModes();
set_expected_cast_result(mojom::RouteRequestResultCode::OK);
auto expected_result =
GetSuccessResult(default_presentation_request(), cast_sink().id());
EXPECT_CALL(*presentation_manager(),
OnPresentationResponse(default_presentation_request(), _, _));
StartPresentation(cast_sink(), default_presentation_request());
EXPECT_EQ(mojom::RouteRequestResultCode::OK,
route_request_result()->result_code());
EXPECT_EQ(kDefaultPresentationUrl,
route_request_result()->presentation_url());
}
// Demonstrates that failures to create presentation routes from web content are
// propagated correctly.
TEST_F(MediaRouteStarterTest, StartRoute_WebContentPresentationError) {
presentation_manager()->SetDefaultPresentationRequest(
default_presentation_request());
CreateStarterForDefaultModes();
set_expected_cast_result(mojom::RouteRequestResultCode::INVALID_ORIGIN);
EXPECT_CALL(*presentation_manager(),
OnPresentationResponse(default_presentation_request(), _, _));
StartPresentation(cast_sink(), default_presentation_request());
EXPECT_EQ(mojom::RouteRequestResultCode::INVALID_ORIGIN,
route_request_result()->result_code());
}
// Demonstrates that presentations routes from start presentation contexts are
// created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_StartPresentationContext_Cast) {
auto start_presentation_context =
CreateStartPresentationContext(start_presentation_request());
CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
std::move(start_presentation_context)));
set_expected_cast_result(mojom::RouteRequestResultCode::OK);
auto expected_result =
GetSuccessResult(start_presentation_request(), cast_sink().id());
EXPECT_CALL(*this, RequestSuccess(_, _, *expected_result->route()));
StartPresentation(cast_sink(), start_presentation_request());
EXPECT_EQ(mojom::RouteRequestResultCode::OK,
route_request_result()->result_code());
EXPECT_EQ(kStartPresentationUrl, route_request_result()->presentation_url());
}
TEST_F(MediaRouteStarterTest,
StartRoute_StartPresentationContext_RemotePlayback) {
auto start_presentation_context = CreateStartPresentationContext(
CreatePresentationRequest(kRemotePlaybackUrl, kStartOriginUrl));
CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
std::move(start_presentation_context)));
set_expected_cast_result(mojom::RouteRequestResultCode::OK);
EXPECT_CALL(*this, RequestSuccess(_, _, _));
StartRemotePlayback(cast_sink());
// TODO(crbug.com/1491212): Update test case once `tab_id` is removed from the
// Remote Playback presentation url.
EXPECT_EQ(mojom::RouteRequestResultCode::OK,
route_request_result()->result_code());
EXPECT_EQ(
base::StrCat(
{kRemotePlaybackUrl, "&tab_id=",
base::NumberToString(
sessions::SessionTabHelper::IdForTab(web_contents()).id())}),
route_request_result()->presentation_url());
}
// Demonstrates that failures to create presentation routes from start
// presentation contexts are created correctly.
TEST_F(MediaRouteStarterTest, StartRoute_StartPresentationContextError) {
auto start_presentation_context =
CreateStartPresentationContext(start_presentation_request());
CreateStarter(MediaRouterUIParameters(kDefaultModes, web_contents(),
std::move(start_presentation_context)));
set_expected_cast_result(
mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER);
EXPECT_CALL(*this, RequestError(_));
StartPresentation(cast_sink(), start_presentation_request());
EXPECT_EQ(mojom::RouteRequestResultCode::NO_SUPPORTED_PROVIDER,
route_request_result()->result_code());
}
TEST_F(MediaRouteStarterTest, GetScreenCapturePermission) {
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
bool screen_capture_is_allowed = false;
#else
bool screen_capture_is_allowed = true;
#endif // BUILDFLAG(IS_MAC)
set_screen_capture_allowed_for_testing(true);
// Always allowed for presentation mode and tab mirroring.
EXPECT_TRUE(MediaRouteStarter::GetScreenCapturePermission(
MediaCastMode::PRESENTATION));
EXPECT_TRUE(
MediaRouteStarter::GetScreenCapturePermission(MediaCastMode::TAB_MIRROR));
// Always allowed for desktop mode if permission has been granted
EXPECT_TRUE(MediaRouteStarter::GetScreenCapturePermission(
MediaCastMode::DESKTOP_MIRROR));
set_screen_capture_allowed_for_testing(false);
// Always allowed for presentation mode and tab mirroring.
EXPECT_TRUE(MediaRouteStarter::GetScreenCapturePermission(
MediaCastMode::PRESENTATION));
EXPECT_TRUE(
MediaRouteStarter::GetScreenCapturePermission(MediaCastMode::TAB_MIRROR));
// The question of whether permission needs to be granted depends on platform
// and version.
EXPECT_EQ(screen_capture_is_allowed,
MediaRouteStarter::GetScreenCapturePermission(
MediaCastMode::DESKTOP_MIRROR));
}
} // namespace media_router