Commit 21d9a27d authored by btolsch's avatar btolsch Committed by Commit bot

[Media Router] Return NotFoundError on dialog exit if appropriate

The MR dialog will always be opened when starting a PresentationRequest,
even if there are no sinks that support the presentation url.  The
default cast mode will then be some form of mirroring.  This means that
whether the user selects a device in the dialog or not, the promise from
start() will be rejected with an AbortError.  Instead, a NotFoundError
should be used if there were no devices which supported the presentation
url.

BUG=621161

Review-Url: https://codereview.chromium.org/2108973002
Cr-Commit-Position: refs/heads/master@{#403336}
parent 9c8d8e76
......@@ -205,9 +205,19 @@ MediaRouterUI::~MediaRouterUI() {
// If |create_session_request_| still exists, then it means presentation route
// request was never attempted.
if (create_session_request_) {
create_session_request_->InvokeErrorCallback(content::PresentationError(
content::PRESENTATION_ERROR_SESSION_REQUEST_CANCELLED,
"Dialog closed."));
bool presentation_sinks_available = std::any_of(
sinks_.begin(), sinks_.end(), [](const MediaSinkWithCastModes& sink) {
return ContainsValue(sink.cast_modes, MediaCastMode::DEFAULT);
});
if (presentation_sinks_available) {
create_session_request_->InvokeErrorCallback(content::PresentationError(
content::PRESENTATION_ERROR_SESSION_REQUEST_CANCELLED,
"Dialog closed."));
} else {
create_session_request_->InvokeErrorCallback(content::PresentationError(
content::PRESENTATION_ERROR_NO_AVAILABLE_SCREENS,
"No screens found."));
}
}
}
......@@ -286,12 +296,20 @@ void MediaRouterUI::InitCommon(content::WebContents* initiator) {
UpdateCastModes();
}
void MediaRouterUI::InitForTest(MediaRouter* router,
content::WebContents* initiator,
MediaRouterWebUIMessageHandler* handler) {
void MediaRouterUI::InitForTest(
MediaRouter* router,
content::WebContents* initiator,
MediaRouterWebUIMessageHandler* handler,
std::unique_ptr<CreatePresentationConnectionRequest>
create_session_request) {
router_ = router;
handler_ = handler;
create_session_request_ = std::move(create_session_request);
InitCommon(initiator);
if (create_session_request_) {
OnDefaultPresentationChanged(
create_session_request_->presentation_request());
}
}
void MediaRouterUI::OnDefaultPresentationChanged(
......
......@@ -155,7 +155,9 @@ class MediaRouterUI : public ConstrainedWebDialogUI,
void InitForTest(MediaRouter* router,
content::WebContents* initiator,
MediaRouterWebUIMessageHandler* handler);
MediaRouterWebUIMessageHandler* handler,
std::unique_ptr<CreatePresentationConnectionRequest>
create_session_request);
private:
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, SortedSinks);
......
......@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/media/router/create_presentation_connection_request.h"
#include "chrome/browser/media/router/media_route.h"
#include "chrome/browser/media/router/media_source_helper.h"
#include "chrome/browser/media/router/mock_media_router.h"
......@@ -29,11 +30,30 @@
using testing::_;
using testing::AnyNumber;
using testing::Invoke;
using testing::SaveArg;
using testing::Return;
namespace media_router {
class PresentationRequestCallbacks {
public:
explicit PresentationRequestCallbacks(
const content::PresentationError& expected_error)
: expected_error_(expected_error) {}
void Success(const content::PresentationSessionInfo&, const MediaRoute::Id&) {
}
void Error(const content::PresentationError& error) {
EXPECT_EQ(expected_error_.error_type, error.error_type);
EXPECT_EQ(expected_error_.message, error.message);
}
private:
content::PresentationError expected_error_;
};
class MockRoutesUpdatedCallback {
public:
MOCK_METHOD2(OnRoutesUpdated,
......@@ -61,11 +81,15 @@ class MediaRouterUITest : public ::testing::Test {
message_handler_.reset(
new MediaRouterWebUIMessageHandler(media_router_ui_.get()));
EXPECT_CALL(mock_router_, RegisterMediaSinksObserver(_))
.WillRepeatedly(Return(true));
.WillRepeatedly(Invoke([this](MediaSinksObserver* observer) {
this->media_sinks_observers_.push_back(observer);
return true;
}));
EXPECT_CALL(mock_router_, RegisterMediaRoutesObserver(_))
.Times(AnyNumber());
media_router_ui_->InitForTest(&mock_router_, initiator_.get(),
message_handler_.get());
message_handler_.get(),
std::move(create_session_request_));
message_handler_->SetWebUIForTest(&web_ui_);
}
......@@ -76,8 +100,10 @@ class MediaRouterUITest : public ::testing::Test {
std::unique_ptr<content::WebContents> initiator_;
content::TestWebUI web_ui_;
std::unique_ptr<content::WebContents> web_contents_;
std::unique_ptr<CreatePresentationConnectionRequest> create_session_request_;
std::unique_ptr<MediaRouterUI> media_router_ui_;
std::unique_ptr<MediaRouterWebUIMessageHandler> message_handler_;
std::vector<MediaSinksObserver*> media_sinks_observers_;
};
TEST_F(MediaRouterUITest, RouteCreationTimeoutForTab) {
......@@ -406,4 +432,81 @@ TEST_F(MediaRouterUITest, GetExtensionNameEmptyWhenNotExtensionURL) {
EXPECT_EQ("", MediaRouterUI::GetExtensionName(url, registry.get()));
}
TEST_F(MediaRouterUITest, NotFoundErrorOnCloseWithNoSinks) {
content::PresentationError expected_error(
content::PresentationErrorType::PRESENTATION_ERROR_NO_AVAILABLE_SCREENS,
"No screens found.");
PresentationRequestCallbacks request_callbacks(expected_error);
create_session_request_.reset(new CreatePresentationConnectionRequest(
RenderFrameHostId(0, 0), std::string("http://google.com/presentation"),
GURL("http://google.com"),
base::Bind(&PresentationRequestCallbacks::Success,
base::Unretained(&request_callbacks)),
base::Bind(&PresentationRequestCallbacks::Error,
base::Unretained(&request_callbacks))));
CreateMediaRouterUI(&profile_);
// Destroying the UI should return the expected error from above to the error
// callback.
media_router_ui_.reset();
}
TEST_F(MediaRouterUITest, NotFoundErrorOnCloseWithNoCompatibleSinks) {
content::PresentationError expected_error(
content::PresentationErrorType::PRESENTATION_ERROR_NO_AVAILABLE_SCREENS,
"No screens found.");
PresentationRequestCallbacks request_callbacks(expected_error);
std::string presentation_url("http://google.com/presentation");
create_session_request_.reset(new CreatePresentationConnectionRequest(
RenderFrameHostId(0, 0), presentation_url, GURL("http://google.com"),
base::Bind(&PresentationRequestCallbacks::Success,
base::Unretained(&request_callbacks)),
base::Bind(&PresentationRequestCallbacks::Error,
base::Unretained(&request_callbacks))));
CreateMediaRouterUI(&profile_);
// Send a sink to the UI that is compatible with sources other than the
// presentation url to cause a NotFoundError.
std::vector<MediaSink> sinks;
sinks.emplace_back("sink id", "sink name", MediaSink::GENERIC);
std::vector<GURL> origins;
for (auto& observer : media_sinks_observers_) {
if (observer->source().id() != presentation_url) {
observer->OnSinksUpdated(sinks, origins);
}
}
// Destroying the UI should return the expected error from above to the error
// callback.
media_router_ui_.reset();
}
TEST_F(MediaRouterUITest, AbortErrorOnClose) {
content::PresentationError expected_error(
content::PresentationErrorType::
PRESENTATION_ERROR_SESSION_REQUEST_CANCELLED,
"Dialog closed.");
PresentationRequestCallbacks request_callbacks(expected_error);
std::string presentation_url("http://google.com/presentation");
create_session_request_.reset(new CreatePresentationConnectionRequest(
RenderFrameHostId(0, 0), presentation_url, GURL("http://google.com"),
base::Bind(&PresentationRequestCallbacks::Success,
base::Unretained(&request_callbacks)),
base::Bind(&PresentationRequestCallbacks::Error,
base::Unretained(&request_callbacks))));
CreateMediaRouterUI(&profile_);
// Send a sink to the UI that is compatible with the presentation url to avoid
// a NotFoundError.
std::vector<MediaSink> sinks;
sinks.emplace_back("sink id", "sink name", MediaSink::GENERIC);
std::vector<GURL> origins;
for (auto& observer : media_sinks_observers_) {
if (observer->source().id() == presentation_url) {
observer->OnSinksUpdated(sinks, origins);
}
}
// Destroying the UI should return the expected error from above to the error
// callback.
media_router_ui_.reset();
}
} // namespace media_router
......@@ -11,6 +11,8 @@ media_router_integration_test_resources = [
"resources/fail_reconnect_session.html",
"resources/fail_reconnect_session.json",
"resources/no_provider.json",
"resources/no_sinks.json",
"resources/no_supported_sinks.json",
"resources/route_creation_timed_out.json",
]
......
......@@ -568,4 +568,39 @@ IN_PROC_BROWSER_TEST_F(MediaRouterIntegrationBrowserTest,
CheckStartFailed(web_contents, "AbortError", "Dialog closed.");
}
IN_PROC_BROWSER_TEST_F(MediaRouterIntegrationBrowserTest,
MANUAL_Fail_StartCancelledNoSinks) {
SetTestData(FILE_PATH_LITERAL("no_sinks.json"));
OpenTestPage(FILE_PATH_LITERAL("basic_test.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::TestNavigationObserver test_navigation_observer(web_contents, 1);
StartSession(web_contents);
MediaRouterDialogControllerImpl* controller =
MediaRouterDialogControllerImpl::GetOrCreateForWebContents(web_contents);
EXPECT_TRUE(controller->IsShowingMediaRouterDialog());
controller->HideMediaRouterDialog();
CheckStartFailed(web_contents, "NotFoundError", "No screens found.");
}
IN_PROC_BROWSER_TEST_F(MediaRouterIntegrationBrowserTest,
MANUAL_Fail_StartCancelledNoSupportedSinks) {
SetTestData(FILE_PATH_LITERAL("no_supported_sinks.json"));
OpenTestPage(FILE_PATH_LITERAL("basic_test.html"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
ASSERT_TRUE(web_contents);
content::TestNavigationObserver test_navigation_observer(web_contents, 1);
StartSession(web_contents);
MediaRouterDialogControllerImpl* controller =
MediaRouterDialogControllerImpl::GetOrCreateForWebContents(web_contents);
EXPECT_TRUE(controller->IsShowingMediaRouterDialog());
WaitUntilSinkDiscoveredOnUI();
controller->HideMediaRouterDialog();
CheckStartFailed(web_contents, "NotFoundError", "No screens found.");
}
} // namespace media_router
......@@ -12,6 +12,8 @@
'resources/fail_reconnect_session.html',
'resources/fail_reconnect_session.json',
'resources/no_provider.json',
'resources/no_sinks.json',
'resources/no_supported_sinks.json',
'resources/route_creation_timed_out.json',
],
'media_router_test_extension_resources': [
......
......@@ -6,11 +6,16 @@ The test data is a JSON string and here is full example with description:
{
// Define the return value for getAvailableSinks API defined in
// TestProvider.js.
// The return value is a list as following. Default value is:
// The value is a map from source urn to a list of sinks as following. Default
// value is for test source urn only and is:
// [{"id": "id1", "friendlyName": "test-sink-1"},
// {"id": "id2", "friendlyName": "test-sink-2"}]
"getAvailableSinks": [{"id": "id1", "friendlyName": "test-device-1"},
{"id": "id2", "friendlyName": "test-device-2"}],
"getAvailableSinks": {
"http://www.google.com/": [
{"id": "id1", "friendlyName": "test-device-1"},
{"id": "id2", "friendlyName": "test-device-2"}
]
},
// Define the return value for canRoute API, the return value should be
// either 'true' or 'false'. The default value is 'true'.
......
{
"getAvailableSinks": {
"urn:x-org.chromium.media:source:desktop":
[{"id" : "id1", "friendlyName" : "test-sink-1"}]
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment