blob: feada7c6b1d350e8fb81f6ead282191fb16cea27 [file] [log] [blame]
// Copyright 2015 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 "chrome/browser/ui/webui/media_router/media_router_ui.h"
#include <algorithm>
#include <string>
#include <unordered_map>
#include <utility>
#include "base/guid.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/media/router/create_presentation_connection_request.h"
#include "chrome/browser/media/router/event_page_request_manager.h"
#include "chrome/browser/media/router/event_page_request_manager_factory.h"
#include "chrome/browser/media/router/issues_observer.h"
#include "chrome/browser/media/router/media_router.h"
#include "chrome/browser/media/router/media_router_factory.h"
#include "chrome/browser/media/router/media_router_feature.h"
#include "chrome/browser/media/router/media_router_metrics.h"
#include "chrome/browser/media/router/media_routes_observer.h"
#include "chrome/browser/media/router/media_sinks_observer.h"
#include "chrome/browser/media/router/mojo/media_router_mojo_impl.h"
#include "chrome/browser/media/router/presentation_service_delegate_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.h"
#include "chrome/browser/ui/webui/media_router/media_router_resources_provider.h"
#include "chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/media_router/issue.h"
#include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/media_sink.h"
#include "chrome/common/media_router/media_source.h"
#include "chrome/common/media_router/media_source_helper.h"
#include "chrome/common/media_router/route_request_result.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "third_party/icu/source/i18n/unicode/coll.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/web_dialogs/web_dialog_delegate.h"
#include "url/origin.h"
namespace media_router {
namespace {
// The amount of time to wait for a response when creating a new route.
const int kCreateRouteTimeoutSeconds = 20;
const int kCreateRouteTimeoutSecondsForTab = 60;
const int kCreateRouteTimeoutSecondsForLocalFile = 60;
const int kCreateRouteTimeoutSecondsForDesktop = 120;
std::string GetHostFromURL(const GURL& gurl) {
if (gurl.is_empty())
return std::string();
std::string host = gurl.host();
if (base::StartsWith(host, "www.", base::CompareCase::INSENSITIVE_ASCII))
host = host.substr(4);
return host;
}
std::string TruncateHost(const std::string& host) {
const std::string truncated =
net::registry_controlled_domains::GetDomainAndRegistry(
host, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
// The truncation will be empty in some scenarios (e.g. host is
// simply an IP address). Fail gracefully.
return truncated.empty() ? host : truncated;
}
base::TimeDelta GetRouteRequestTimeout(MediaCastMode cast_mode) {
switch (cast_mode) {
case PRESENTATION:
return base::TimeDelta::FromSeconds(kCreateRouteTimeoutSeconds);
case TAB_MIRROR:
return base::TimeDelta::FromSeconds(kCreateRouteTimeoutSecondsForTab);
case DESKTOP_MIRROR:
return base::TimeDelta::FromSeconds(kCreateRouteTimeoutSecondsForDesktop);
case LOCAL_FILE:
return base::TimeDelta::FromSeconds(
kCreateRouteTimeoutSecondsForLocalFile);
default:
NOTREACHED();
return base::TimeDelta();
}
}
// Returns the first source in |sources| that can be connected to by using the
// "Cast" button in the dialog, or an empty source if there is none. This is
// used by the Media Router to find such a matching route if it exists.
MediaSource GetSourceForRouteObserver(const std::vector<MediaSource>& sources) {
auto source_it =
std::find_if(sources.begin(), sources.end(), CanConnectToMediaSource);
return source_it != sources.end() ? *source_it : MediaSource("");
}
} // namespace
// static
std::string MediaRouterUI::GetExtensionName(
const GURL& gurl,
extensions::ExtensionRegistry* registry) {
if (gurl.is_empty() || !registry)
return std::string();
const extensions::Extension* extension =
registry->enabled_extensions().GetExtensionOrAppByURL(gurl);
return extension ? extension->name() : std::string();
}
Browser* MediaRouterUI::GetBrowser() {
return chrome::FindBrowserWithWebContents(initiator());
}
void MediaRouterUI::OpenTabWithUrl(const GURL url) {
// Check if the current page is a new tab. If so open file in current page.
// If not then open a new page.
if (initiator_->GetVisibleURL() == chrome::kChromeUINewTabURL) {
content::NavigationController::LoadURLParams load_params(url);
load_params.transition_type = ui::PAGE_TRANSITION_GENERATED;
initiator_->GetController().LoadURLWithParams(load_params);
} else {
initiator_ = chrome::AddSelectedTabWithURL(GetBrowser(), url,
ui::PAGE_TRANSITION_LINK);
}
}
void MediaRouterUI::FileDialogFileSelected(
const ui::SelectedFileInfo& file_info) {
handler_->UserSelectedLocalMediaFile(file_info.display_name);
}
void MediaRouterUI::FileDialogSelectionFailed(const IssueInfo& issue) {
AddIssue(issue);
}
// This class calls to refresh the UI when the highest priority issue is
// updated.
class MediaRouterUI::UIIssuesObserver : public IssuesObserver {
public:
UIIssuesObserver(MediaRouter* router, MediaRouterUI* ui)
: IssuesObserver(router), ui_(ui) {
DCHECK(ui);
}
~UIIssuesObserver() override {}
// IssuesObserver implementation.
void OnIssue(const Issue& issue) override { ui_->SetIssue(issue); }
void OnIssuesCleared() override { ui_->ClearIssue(); }
private:
// Reference back to the owning MediaRouterUI instance.
MediaRouterUI* ui_;
DISALLOW_COPY_AND_ASSIGN(UIIssuesObserver);
};
MediaRouterUI::UIMediaRoutesObserver::UIMediaRoutesObserver(
MediaRouter* router,
const MediaSource::Id& source_id,
const RoutesUpdatedCallback& callback)
: MediaRoutesObserver(router, source_id), callback_(callback) {
DCHECK(!callback_.is_null());
}
MediaRouterUI::UIMediaRoutesObserver::~UIMediaRoutesObserver() {}
void MediaRouterUI::UIMediaRoutesObserver::OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) {
callback_.Run(routes, joinable_route_ids);
}
MediaRouterUI::UIMediaRouteControllerObserver::UIMediaRouteControllerObserver(
MediaRouterUI* ui,
scoped_refptr<MediaRouteController> controller)
: MediaRouteController::Observer(std::move(controller)), ui_(ui) {
if (controller_->current_media_status())
OnMediaStatusUpdated(controller_->current_media_status().value());
}
MediaRouterUI::UIMediaRouteControllerObserver::
~UIMediaRouteControllerObserver() {}
void MediaRouterUI::UIMediaRouteControllerObserver::OnMediaStatusUpdated(
const MediaStatus& status) {
ui_->UpdateMediaRouteStatus(status);
}
void MediaRouterUI::UIMediaRouteControllerObserver::OnControllerInvalidated() {
ui_->OnRouteControllerInvalidated();
}
MediaRouterUI::MediaRouterUI(content::WebUI* web_ui)
: ConstrainedWebDialogUI(web_ui),
ui_initialized_(false),
current_route_request_id_(-1),
route_request_counter_(0),
initiator_(nullptr),
router_(nullptr),
weak_factory_(this) {
auto handler = base::MakeUnique<MediaRouterWebUIMessageHandler>(this);
handler_ = handler.get();
// Create a WebUIDataSource containing the chrome://media-router page's
// content.
std::unique_ptr<content::WebUIDataSource> html_source(
content::WebUIDataSource::Create(chrome::kChromeUIMediaRouterHost));
content::WebContents* wc = web_ui->GetWebContents();
DCHECK(wc);
content::BrowserContext* context = wc->GetBrowserContext();
router_ = MediaRouterFactory::GetApiForBrowserContext(context);
event_page_request_manager_ =
EventPageRequestManagerFactory::GetApiForBrowserContext(context);
// Allows UI to load extensionview.
// TODO(haibinlu): limit object-src to current extension once crbug/514866
// is fixed.
html_source->OverrideContentSecurityPolicyObjectSrc("object-src chrome:;");
AddLocalizedStrings(html_source.get());
AddMediaRouterUIResources(html_source.get());
// Ownership of |html_source| is transferred to the BrowserContext.
content::WebUIDataSource::Add(Profile::FromWebUI(web_ui),
html_source.release());
web_ui->AddMessageHandler(std::move(handler));
}
MediaRouterUI::~MediaRouterUI() {
if (query_result_manager_.get())
query_result_manager_->RemoveObserver(this);
if (presentation_service_delegate_.get())
presentation_service_delegate_->RemoveDefaultPresentationRequestObserver(
this);
// If |create_session_request_| still exists, then it means presentation route
// request was never attempted.
if (create_session_request_) {
bool presentation_sinks_available = std::any_of(
sinks_.begin(), sinks_.end(), [](const MediaSinkWithCastModes& sink) {
return base::ContainsKey(sink.cast_modes,
MediaCastMode::PRESENTATION);
});
if (presentation_sinks_available) {
create_session_request_->InvokeErrorCallback(content::PresentationError(
content::PRESENTATION_ERROR_PRESENTATION_REQUEST_CANCELLED,
"Dialog closed."));
} else {
create_session_request_->InvokeErrorCallback(content::PresentationError(
content::PRESENTATION_ERROR_NO_AVAILABLE_SCREENS,
"No screens found."));
}
}
}
void MediaRouterUI::InitWithDefaultMediaSource(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate) {
DCHECK(initiator);
DCHECK(!presentation_service_delegate_);
DCHECK(!query_result_manager_.get());
InitCommon(initiator);
if (delegate) {
presentation_service_delegate_ = delegate->GetWeakPtr();
presentation_service_delegate_->AddDefaultPresentationRequestObserver(this);
}
if (delegate && delegate->HasDefaultPresentationRequest()) {
OnDefaultPresentationChanged(delegate->GetDefaultPresentationRequest());
} else {
// Register for MediaRoute updates without a media source.
routes_observer_.reset(new UIMediaRoutesObserver(
router_, MediaSource::Id(),
base::Bind(&MediaRouterUI::OnRoutesUpdated, base::Unretained(this))));
}
}
void MediaRouterUI::InitWithPresentationSessionRequest(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate,
std::unique_ptr<CreatePresentationConnectionRequest>
create_session_request) {
DCHECK(initiator);
DCHECK(delegate);
DCHECK(create_session_request);
DCHECK(!create_session_request_);
DCHECK(!query_result_manager_);
create_session_request_ = std::move(create_session_request);
presentation_service_delegate_ = delegate->GetWeakPtr();
InitCommon(initiator);
OnDefaultPresentationChanged(create_session_request_->presentation_request());
}
void MediaRouterUI::InitCommon(content::WebContents* initiator) {
DCHECK(initiator);
DCHECK(router_);
TRACE_EVENT_NESTABLE_ASYNC_INSTANT1("media_router", "UI", initiator,
"MediaRouterUI::InitCommon", this);
// Presentation requests from content must show the origin requesting
// presentation: crbug.com/704964
if (create_session_request_)
forced_cast_mode_ = MediaCastMode::PRESENTATION;
router_->OnUserGesture();
// Create |collator_| before |query_result_manager_| so that |collator_| is
// already set up when we get a callback from |query_result_manager_|.
UErrorCode error = U_ZERO_ERROR;
const std::string& locale = g_browser_process->GetApplicationLocale();
collator_.reset(
icu::Collator::createInstance(icu::Locale(locale.c_str()), error));
if (U_FAILURE(error)) {
DLOG(ERROR) << "Failed to create collator for locale " << locale;
collator_.reset();
}
query_result_manager_.reset(new QueryResultManager(router_));
query_result_manager_->AddObserver(this);
// Use a placeholder URL as origin for mirroring.
url::Origin origin{GURL(chrome::kChromeUIMediaRouterURL)};
// Desktop mirror mode is always available.
query_result_manager_->SetSourcesForCastMode(
MediaCastMode::DESKTOP_MIRROR, {MediaSourceForDesktop()}, origin);
// For now, file mirroring is always availible if enabled.
if (CastLocalMediaEnabled()) {
query_result_manager_->SetSourcesForCastMode(
MediaCastMode::LOCAL_FILE, {MediaSourceForTab(0)}, origin);
}
initiator_ = initiator;
SessionID::id_type tab_id = SessionTabHelper::IdForTab(initiator);
if (tab_id != -1) {
MediaSource mirroring_source(MediaSourceForTab(tab_id));
query_result_manager_->SetSourcesForCastMode(MediaCastMode::TAB_MIRROR,
{mirroring_source}, origin);
}
UpdateCastModes();
// Get the current list of media routes, so that the WebUI will have routes
// information at initialization.
OnRoutesUpdated(router_->GetCurrentRoutes(), std::vector<MediaRoute::Id>());
}
void MediaRouterUI::InitForTest(
MediaRouter* router,
content::WebContents* initiator,
MediaRouterWebUIMessageHandler* handler,
std::unique_ptr<CreatePresentationConnectionRequest> create_session_request,
std::unique_ptr<MediaRouterFileDialog> file_dialog) {
router_ = router;
handler_ = handler;
create_session_request_ = std::move(create_session_request);
media_router_file_dialog_ = std::move(file_dialog);
InitCommon(initiator);
if (create_session_request_) {
OnDefaultPresentationChanged(
create_session_request_->presentation_request());
}
}
void MediaRouterUI::OnDefaultPresentationChanged(
const content::PresentationRequest& presentation_request) {
std::vector<MediaSource> sources =
MediaSourcesForPresentationUrls(presentation_request.presentation_urls);
presentation_request_ = presentation_request;
query_result_manager_->SetSourcesForCastMode(
MediaCastMode::PRESENTATION, sources,
presentation_request_->frame_origin);
// Register for MediaRoute updates. NOTE(mfoltz): If there are multiple
// sources that can be connected to via the dialog, this will break. We will
// need to observe multiple sources (keyed by sinks) in that case. As this is
// Cast-specific for the forseeable future, it may be simpler to plumb a new
// observer API for this case.
const MediaSource source_for_route_observer =
GetSourceForRouteObserver(sources);
routes_observer_.reset(new UIMediaRoutesObserver(
router_, source_for_route_observer.id(),
base::Bind(&MediaRouterUI::OnRoutesUpdated, base::Unretained(this))));
UpdateCastModes();
}
void MediaRouterUI::OnDefaultPresentationRemoved() {
presentation_request_.reset();
query_result_manager_->RemoveSourcesForCastMode(MediaCastMode::PRESENTATION);
// This should not be set if the dialog was initiated with a default
// presentation request from the top level frame. However, clear it just to
// be safe.
forced_cast_mode_ = base::nullopt;
// Register for MediaRoute updates without a media source.
routes_observer_.reset(new UIMediaRoutesObserver(
router_, MediaSource::Id(),
base::Bind(&MediaRouterUI::OnRoutesUpdated, base::Unretained(this))));
UpdateCastModes();
}
void MediaRouterUI::UpdateCastModes() {
// Gets updated cast modes from |query_result_manager_| and forwards it to UI.
cast_modes_ = query_result_manager_->GetSupportedCastModes();
if (ui_initialized_) {
handler_->UpdateCastModes(cast_modes_, GetPresentationRequestSourceName(),
forced_cast_mode());
}
}
void MediaRouterUI::UpdateRoutesToCastModesMapping() {
std::unordered_map<MediaSource::Id, MediaCastMode> available_source_map;
for (const auto& cast_mode : cast_modes_) {
for (const auto& source :
query_result_manager_->GetSourcesForCastMode(cast_mode)) {
available_source_map.insert(std::make_pair(source.id(), cast_mode));
}
}
routes_and_cast_modes_.clear();
for (const auto& route : routes_) {
auto source_entry = available_source_map.find(route.media_source().id());
if (source_entry != available_source_map.end()) {
routes_and_cast_modes_.insert(
std::make_pair(route.media_route_id(), source_entry->second));
}
}
}
void MediaRouterUI::Close() {
ConstrainedWebDialogDelegate* delegate = GetConstrainedDelegate();
if (delegate) {
delegate->GetWebDialogDelegate()->OnDialogClosed(std::string());
delegate->OnDialogCloseFromWebUI();
}
}
void MediaRouterUI::UIInitialized() {
TRACE_EVENT_NESTABLE_ASYNC_END0("media_router", "UI", initiator_);
ui_initialized_ = true;
// Register for Issue updates.
issues_observer_.reset(new UIIssuesObserver(router_, this));
issues_observer_->Init();
}
bool MediaRouterUI::CreateRoute(const MediaSink::Id& sink_id,
MediaCastMode cast_mode) {
MediaSource::Id source_id;
url::Origin origin;
std::vector<MediaRouteResponseCallback> route_response_callbacks;
base::TimeDelta timeout;
bool incognito;
if (cast_mode == MediaCastMode::LOCAL_FILE) {
GURL url = media_router_file_dialog_->GetLastSelectedFileUrl();
OpenTabWithUrl(url);
}
if (!SetRouteParameters(sink_id, cast_mode, &source_id, &origin,
&route_response_callbacks, &timeout, &incognito)) {
SendIssueForUnableToCast(cast_mode);
return false;
}
router_->CreateRoute(source_id, sink_id, origin, initiator_,
std::move(route_response_callbacks), timeout, incognito);
return true;
}
bool MediaRouterUI::SetRouteParameters(
const MediaSink::Id& sink_id,
MediaCastMode cast_mode,
MediaSource::Id* source_id,
url::Origin* origin,
std::vector<MediaRouteResponseCallback>* route_response_callbacks,
base::TimeDelta* timeout,
bool* incognito) {
DCHECK(query_result_manager_.get());
DCHECK(initiator_);
// Note that there is a rarely-encountered bug, where the MediaCastMode to
// MediaSource mapping could have been updated, between when the user clicked
// on the UI to start a create route request, and when this function is
// called. However, since the user does not have visibility into the
// MediaSource, and that it occurs very rarely in practice, we leave it as-is
// for now.
std::unique_ptr<MediaSource> source =
query_result_manager_->GetSourceForCastModeAndSink(cast_mode, sink_id);
if (!source) {
LOG(ERROR) << "No corresponding MediaSource for cast mode "
<< static_cast<int>(cast_mode) << " and sink " << sink_id;
return false;
}
*source_id = source->id();
bool for_presentation_source = cast_mode == MediaCastMode::PRESENTATION;
if (for_presentation_source && !presentation_request_) {
DLOG(ERROR) << "Requested to create a route for presentation, but "
<< "presentation request is missing.";
return false;
}
current_route_request_id_ = ++route_request_counter_;
*origin = for_presentation_source
? presentation_request_->frame_origin
: url::Origin(GURL(chrome::kChromeUIMediaRouterURL));
DVLOG(1) << "DoCreateRoute: origin: " << *origin;
// There are 3 cases. In cases (1) and (3) the MediaRouterUI will need to be
// notified. In case (2) the dialog will be closed.
// (1) Non-presentation route request (e.g., mirroring). No additional
// notification necessary.
// (2) Presentation route request for a Presentation API startSession call.
// The startSession (CreatePresentationConnectionRequest) will need to be
// answered with the route response.
// (3) Browser-initiated presentation route request. If successful,
// PresentationServiceDelegateImpl will have to be notified. Note that we
// treat subsequent route requests from a Presentation API-initiated dialogs
// as browser-initiated.
if (!for_presentation_source || !create_session_request_) {
route_response_callbacks->push_back(base::BindOnce(
&MediaRouterUI::OnRouteResponseReceived, weak_factory_.GetWeakPtr(),
current_route_request_id_, sink_id, cast_mode,
base::UTF8ToUTF16(GetTruncatedPresentationRequestSourceName())));
}
if (for_presentation_source) {
if (create_session_request_) {
// |create_session_request_| will be nullptr after this call, as the
// object will be transferred to the callback.
route_response_callbacks->push_back(base::BindOnce(
&CreatePresentationConnectionRequest::HandleRouteResponse,
base::Passed(&create_session_request_)));
route_response_callbacks->push_back(base::BindOnce(
&MediaRouterUI::HandleCreateSessionRequestRouteResponse,
weak_factory_.GetWeakPtr()));
} else if (presentation_service_delegate_) {
route_response_callbacks->push_back(base::BindOnce(
&PresentationServiceDelegateImpl::OnRouteResponse,
presentation_service_delegate_, *presentation_request_));
}
}
route_response_callbacks->push_back(
base::BindOnce(&MediaRouterUI::MaybeReportCastingSource,
weak_factory_.GetWeakPtr(), cast_mode));
if (cast_mode == MediaCastMode::LOCAL_FILE) {
route_response_callbacks->push_back(
base::BindOnce(&MediaRouterUI::MaybeReportFileInformation,
weak_factory_.GetWeakPtr()));
}
*timeout = GetRouteRequestTimeout(cast_mode);
*incognito = Profile::FromWebUI(web_ui())->IsOffTheRecord();
return true;
}
bool MediaRouterUI::ConnectRoute(const MediaSink::Id& sink_id,
const MediaRoute::Id& route_id) {
MediaSource::Id source_id;
url::Origin origin;
std::vector<MediaRouteResponseCallback> route_response_callbacks;
base::TimeDelta timeout;
bool incognito;
if (!SetRouteParameters(sink_id, MediaCastMode::PRESENTATION, &source_id,
&origin, &route_response_callbacks, &timeout,
&incognito)) {
SendIssueForUnableToCast(MediaCastMode::PRESENTATION);
return false;
}
router_->ConnectRouteByRouteId(source_id, route_id, origin, initiator_,
std::move(route_response_callbacks), timeout,
incognito);
return true;
}
void MediaRouterUI::CloseRoute(const MediaRoute::Id& route_id) {
router_->TerminateRoute(route_id);
}
void MediaRouterUI::AddIssue(const IssueInfo& issue) {
router_->AddIssue(issue);
}
void MediaRouterUI::ClearIssue(const Issue::Id& issue_id) {
router_->ClearIssue(issue_id);
}
void MediaRouterUI::OpenFileDialog() {
if (!media_router_file_dialog_) {
media_router_file_dialog_ = base::MakeUnique<MediaRouterFileDialog>(this);
}
media_router_file_dialog_->OpenFileDialog(GetBrowser());
}
void MediaRouterUI::SearchSinksAndCreateRoute(
const MediaSink::Id& sink_id,
const std::string& search_criteria,
const std::string& domain,
MediaCastMode cast_mode) {
std::unique_ptr<MediaSource> source =
query_result_manager_->GetSourceForCastModeAndSink(cast_mode, sink_id);
const std::string source_id = source ? source->id() : "";
// The CreateRoute() part of the function is accomplished in the callback
// OnSearchSinkResponseReceived().
router_->SearchSinks(sink_id, source_id, search_criteria, domain,
base::Bind(&MediaRouterUI::OnSearchSinkResponseReceived,
weak_factory_.GetWeakPtr(), cast_mode));
}
bool MediaRouterUI::UserSelectedTabMirroringForCurrentOrigin() const {
const base::ListValue* origins =
Profile::FromWebUI(web_ui())->GetPrefs()->GetList(
prefs::kMediaRouterTabMirroringSources);
return origins->Find(base::Value(GetSerializedInitiatorOrigin())) !=
origins->end();
}
void MediaRouterUI::RecordCastModeSelection(MediaCastMode cast_mode) {
ListPrefUpdate update(Profile::FromWebUI(web_ui())->GetPrefs(),
prefs::kMediaRouterTabMirroringSources);
switch (cast_mode) {
case MediaCastMode::PRESENTATION:
update->Remove(base::Value(GetSerializedInitiatorOrigin()), nullptr);
break;
case MediaCastMode::TAB_MIRROR:
update->AppendIfNotPresent(
base::MakeUnique<base::Value>(GetSerializedInitiatorOrigin()));
break;
case MediaCastMode::DESKTOP_MIRROR:
// Desktop mirroring isn't domain-specific, so we don't record the
// selection.
break;
case MediaCastMode::LOCAL_FILE:
// Local media isn't domain-specific, so we don't record the selection.
break;
default:
NOTREACHED();
break;
}
}
void MediaRouterUI::OnResultsUpdated(
const std::vector<MediaSinkWithCastModes>& sinks) {
sinks_ = sinks;
const icu::Collator* collator_ptr = collator_.get();
std::sort(sinks_.begin(), sinks_.end(),
[collator_ptr](const MediaSinkWithCastModes& sink1,
const MediaSinkWithCastModes& sink2) {
return sink1.sink.CompareUsingCollator(sink2.sink, collator_ptr);
});
if (ui_initialized_)
handler_->UpdateSinks(sinks_);
}
void MediaRouterUI::SetIssue(const Issue& issue) {
if (ui_initialized_)
handler_->UpdateIssue(issue);
}
void MediaRouterUI::ClearIssue() {
if (ui_initialized_)
handler_->ClearIssue();
}
void MediaRouterUI::OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) {
routes_.clear();
joinable_route_ids_.clear();
for (const MediaRoute& route : routes) {
if (route.for_display()) {
#ifndef NDEBUG
for (const MediaRoute& existing_route : routes_) {
if (existing_route.media_sink_id() == route.media_sink_id()) {
DVLOG(2) << "Received another route for display with the same sink"
<< " id as an existing route. " << route.media_route_id()
<< " has the same sink id as "
<< existing_route.media_sink_id() << ".";
}
}
#endif
if (base::ContainsValue(joinable_route_ids, route.media_route_id())) {
joinable_route_ids_.push_back(route.media_route_id());
}
routes_.push_back(route);
}
}
UpdateRoutesToCastModesMapping();
if (ui_initialized_)
handler_->UpdateRoutes(routes_, joinable_route_ids_,
routes_and_cast_modes_);
}
void MediaRouterUI::OnRouteResponseReceived(
int route_request_id,
const MediaSink::Id& sink_id,
MediaCastMode cast_mode,
const base::string16& presentation_request_source_name,
const RouteRequestResult& result) {
DVLOG(1) << "OnRouteResponseReceived";
// If we receive a new route that we aren't expecting, do nothing.
if (route_request_id != current_route_request_id_)
return;
const MediaRoute* route = result.route();
if (!route) {
// The provider will handle sending an issue for a failed route request.
DVLOG(1) << "MediaRouteResponse returned error: " << result.error();
}
handler_->OnCreateRouteResponseReceived(sink_id, route);
current_route_request_id_ = -1;
if (result.result_code() == RouteRequestResult::TIMED_OUT)
SendIssueForRouteTimeout(cast_mode, presentation_request_source_name);
}
void MediaRouterUI::MaybeReportCastingSource(MediaCastMode cast_mode,
const RouteRequestResult& result) {
if (result.result_code() == RouteRequestResult::OK)
MediaRouterMetrics::RecordMediaRouterCastingSource(cast_mode);
}
void MediaRouterUI::MaybeReportFileInformation(
const RouteRequestResult& result) {
if (result.result_code() == RouteRequestResult::OK)
media_router_file_dialog_->MaybeReportLastSelectedFileInformation();
}
void MediaRouterUI::HandleCreateSessionRequestRouteResponse(
const RouteRequestResult&) {
Close();
}
void MediaRouterUI::OnSearchSinkResponseReceived(
MediaCastMode cast_mode,
const MediaSink::Id& found_sink_id) {
DVLOG(1) << "OnSearchSinkResponseReceived";
handler_->ReturnSearchResult(found_sink_id);
MediaSource::Id source_id;
url::Origin origin;
std::vector<MediaRouteResponseCallback> route_response_callbacks;
base::TimeDelta timeout;
bool incognito;
if (!SetRouteParameters(found_sink_id, cast_mode, &source_id, &origin,
&route_response_callbacks, &timeout, &incognito)) {
SendIssueForUnableToCast(cast_mode);
return;
}
router_->CreateRoute(source_id, found_sink_id, origin, initiator_,
std::move(route_response_callbacks), timeout, incognito);
}
void MediaRouterUI::SendIssueForRouteTimeout(
MediaCastMode cast_mode,
const base::string16& presentation_request_source_name) {
std::string issue_title;
switch (cast_mode) {
case PRESENTATION:
DLOG_IF(ERROR, presentation_request_source_name.empty())
<< "Empty presentation request source name.";
issue_title =
l10n_util::GetStringFUTF8(IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT,
presentation_request_source_name);
break;
case TAB_MIRROR:
issue_title = l10n_util::GetStringUTF8(
IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_FOR_TAB);
break;
case DESKTOP_MIRROR:
issue_title = l10n_util::GetStringUTF8(
IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_FOR_DESKTOP);
break;
default:
NOTREACHED();
}
AddIssue(IssueInfo(issue_title, IssueInfo::Action::DISMISS,
IssueInfo::Severity::NOTIFICATION));
}
void MediaRouterUI::SendIssueForUnableToCast(MediaCastMode cast_mode) {
// For a generic error, claim a tab error unless it was specifically desktop
// mirroring.
std::string issue_title =
(cast_mode == MediaCastMode::DESKTOP_MIRROR)
? l10n_util::GetStringUTF8(
IDS_MEDIA_ROUTER_ISSUE_UNABLE_TO_CAST_DESKTOP)
: l10n_util::GetStringUTF8(
IDS_MEDIA_ROUTER_ISSUE_CREATE_ROUTE_TIMEOUT_FOR_TAB);
AddIssue(IssueInfo(issue_title, IssueInfo::Action::DISMISS,
IssueInfo::Severity::WARNING));
}
GURL MediaRouterUI::GetFrameURL() const {
return presentation_request_ ? presentation_request_->frame_origin.GetURL()
: GURL();
}
std::string MediaRouterUI::GetPresentationRequestSourceName() const {
GURL gurl = GetFrameURL();
return gurl.SchemeIs(extensions::kExtensionScheme)
? GetExtensionName(gurl, extensions::ExtensionRegistry::Get(
Profile::FromWebUI(web_ui())))
: GetHostFromURL(gurl);
}
std::string MediaRouterUI::GetTruncatedPresentationRequestSourceName() const {
GURL gurl = GetFrameURL();
return gurl.SchemeIs(extensions::kExtensionScheme)
? GetExtensionName(gurl, extensions::ExtensionRegistry::Get(
Profile::FromWebUI(web_ui())))
: TruncateHost(GetHostFromURL(gurl));
}
const std::set<MediaCastMode>& MediaRouterUI::cast_modes() const {
return cast_modes_;
}
const std::string& MediaRouterUI::GetRouteProviderExtensionId() const {
return event_page_request_manager_->media_route_provider_extension_id();
}
void MediaRouterUI::SetUIInitializationTimer(const base::Time& start_time) {
DCHECK(!start_time.is_null());
start_time_ = start_time;
}
void MediaRouterUI::OnUIInitiallyLoaded() {
if (!start_time_.is_null()) {
MediaRouterMetrics::RecordMediaRouterDialogPaint(base::Time::Now() -
start_time_);
}
}
void MediaRouterUI::OnUIInitialDataReceived() {
if (!start_time_.is_null()) {
MediaRouterMetrics::RecordMediaRouterDialogLoaded(base::Time::Now() -
start_time_);
start_time_ = base::Time();
}
}
void MediaRouterUI::UpdateMaxDialogHeight(int height) {
handler_->UpdateMaxDialogHeight(height);
}
const MediaRouteController* MediaRouterUI::GetMediaRouteController() const {
return route_controller_observer_
? route_controller_observer_->controller().get()
: nullptr;
}
void MediaRouterUI::OnMediaControllerUIAvailable(
const MediaRoute::Id& route_id) {
scoped_refptr<MediaRouteController> controller =
router_->GetRouteController(route_id);
if (!controller) {
DVLOG(1) << "Requested a route controller with an invalid route ID.";
return;
}
DVLOG_IF(1, route_controller_observer_)
<< "Route controller observer unexpectedly exists.";
route_controller_observer_ =
base::MakeUnique<UIMediaRouteControllerObserver>(this, controller);
}
void MediaRouterUI::OnMediaControllerUIClosed() {
route_controller_observer_.reset();
}
void MediaRouterUI::OnRouteControllerInvalidated() {
route_controller_observer_.reset();
handler_->OnRouteControllerInvalidated();
}
void MediaRouterUI::UpdateMediaRouteStatus(const MediaStatus& status) {
handler_->UpdateMediaRouteStatus(status);
}
std::string MediaRouterUI::GetSerializedInitiatorOrigin() const {
url::Origin origin = initiator_
? url::Origin(initiator_->GetLastCommittedURL())
: url::Origin();
return origin.Serialize();
}
} // namespace media_router