blob: 5791e3488f400fd3955f0330f07b3bb3b9274e17 [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/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/media/router/issue_manager.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_metrics.h"
#include "chrome/browser/media/router/media_sinks_observer.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/media_router/media_router_ui_helper.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/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/navigation_handle.h"
#include "content/public/browser/render_frame_host.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 "content/public/common/fullscreen_video_element.mojom.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/blink/public/common/associated_interfaces/associated_interface_provider.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/web_dialogs/web_dialog_delegate.h"
#include "url/origin.h"
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
#include "chrome/browser/media/router/providers/wired_display/wired_display_media_route_provider.h"
#include "ui/display/display.h"
#endif
namespace media_router {
Browser* MediaRouterUI::GetBrowser() {
CHECK(initiator());
return chrome::FindBrowserWithWebContents(initiator());
}
content::WebContents* 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);
return initiator();
} else {
return 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(IssueManager* issue_manager, MediaRouterUI* ui)
: IssuesObserver(issue_manager), 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);
};
// Observes a WebContents and requests fullscreening of its first
// video element. The request is sent after the WebContents is loaded and tab
// capture has begun. Marked final to prevent inheritance so delete calls are
// contained to scenarios documented below.
class MediaRouterUI::WebContentsFullscreenOnLoadedObserver final
: public content::WebContentsObserver {
public:
WebContentsFullscreenOnLoadedObserver(const GURL& file_url,
content::WebContents* web_contents)
: file_url_(file_url), capture_poll_timer_(false, false) {
DCHECK(file_url_.SchemeIsFile());
DCHECK(fullscreen_request_time_.is_null());
// If the WebContents is loading, start listening, otherwise just call the
// fullscreen function.
// This class destroys itself in the following situations (at least one of
// which will occur):
// * after loading is complete and,
// ** capture has begun and fullscreen requested,
// ** kMaxSecondsToWaitForCapture seconds have passed without capture,
// * another navigation is started,
// * the WebContents is destroyed.
if (web_contents->IsLoading()) {
Observe(web_contents);
} else {
FullScreenFirstVideoElement(web_contents);
}
}
~WebContentsFullscreenOnLoadedObserver() override {}
// content::WebContentsObserver implementation.
void DidStopLoading() override {
FullScreenFirstVideoElement(web_contents());
}
void DidStartNavigation(
content::NavigationHandle* navigation_handle) override {
// If the user takes over and navigates away from the file, stop listening.
// (It is possible however for this listener to be created before the
// navigation to the requested file triggers, so provided we're still on the
// same URL, go ahead and keep listening).
if (file_url_ != navigation_handle->GetURL()) {
delete this;
}
}
void WebContentsDestroyed() override {
// If the WebContents is destroyed we will never trigger and need to clean
// up.
delete this;
}
private:
const GURL file_url_;
// Time intervals used by the logic that detects if capture has started.
const int kMaxSecondsToWaitForCapture = 10;
const int kPollIntervalInSeconds = 1;
// The time at which fullscreen was requested.
base::TimeTicks fullscreen_request_time_;
// Poll timer to monitor the capturer count when fullscreening local files.
//
// TODO(crbug.com/540965): Add a method to WebContentsObserver to report
// capturer count changes and get rid of this polling-based approach.
base::Timer capture_poll_timer_;
// Sends a request for full screen to the WebContents targeted at the first
// video element. The request is only sent after capture has begun.
void FullScreenFirstVideoElement(content::WebContents* web_contents) {
if (file_url_ != web_contents->GetLastCommittedURL()) {
// The user has navigated before the casting started. Do not attempt to
// fullscreen and cleanup.
return;
}
fullscreen_request_time_ = base::TimeTicks::Now();
FullscreenIfContentCaptured(web_contents);
}
void FullscreenIfContentCaptured(content::WebContents* web_contents) {
if (web_contents->IsBeingCaptured()) {
content::mojom::FullscreenVideoElementHandlerAssociatedPtr client;
web_contents->GetMainFrame()
->GetRemoteAssociatedInterfaces()
->GetInterface(&client);
client->RequestFullscreenVideoElement();
delete this;
return;
} else if (base::TimeTicks::Now() - fullscreen_request_time_ >
base::TimeDelta::FromSeconds(kMaxSecondsToWaitForCapture)) {
// If content capture hasn't started within the timeout skip fullscreen.
DLOG(WARNING) << "Capture of local content did not start within timeout";
delete this;
return;
}
capture_poll_timer_.Start(
FROM_HERE, base::TimeDelta::FromSeconds(kPollIntervalInSeconds),
base::BindRepeating(
&WebContentsFullscreenOnLoadedObserver::FullscreenIfContentCaptured,
base::Unretained(this), web_contents));
}
};
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),
weak_factory_(this) {
auto handler = std::make_unique<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));
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() = default;
void MediaRouterUI::InitCommon(content::WebContents* initiator) {
MediaRouterUIBase::InitCommon(initiator);
UpdateCastModes();
// Presentation requests from content must show the origin requesting
// presentation: crbug.com/704964
if (start_presentation_context())
forced_cast_mode_ = MediaCastMode::PRESENTATION;
}
void MediaRouterUI::InitForTest(
MediaRouter* router,
content::WebContents* initiator,
MediaRouterWebUIMessageHandler* handler,
std::unique_ptr<StartPresentationContext> context,
std::unique_ptr<MediaRouterFileDialog> file_dialog) {
handler_ = handler;
set_start_presentation_context_for_test(std::move(context));
InitForTest(std::move(file_dialog));
InitCommon(initiator);
if (start_presentation_context()) {
OnDefaultPresentationChanged(
start_presentation_context()->presentation_request());
}
UIInitialized();
}
void MediaRouterUI::InitForTest(
std::unique_ptr<MediaRouterFileDialog> file_dialog) {
media_router_file_dialog_ = std::move(file_dialog);
}
void MediaRouterUI::OnDefaultPresentationChanged(
const content::PresentationRequest& presentation_request) {
MediaRouterUIBase::OnDefaultPresentationChanged(presentation_request);
UpdateCastModes();
}
void MediaRouterUI::OnDefaultPresentationRemoved() {
MediaRouterUIBase::OnDefaultPresentationRemoved();
// 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;
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 : 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());
// TODO(imcheng): We should be able to instantiate |issue_observer_| during
// InitCommon by storing an initial Issue in this class.
// Register for Issue updates.
issues_observer_ =
std::make_unique<UIIssuesObserver>(GetIssueManager(), this);
issues_observer_->Init();
ui_initialized_ = true;
}
bool MediaRouterUI::CreateRoute(const MediaSink::Id& sink_id,
MediaCastMode cast_mode) {
// Default the tab casting the content to the initiator, and change if
// necessary.
content::WebContents* tab_contents = initiator();
base::Optional<RouteParameters> params;
if (cast_mode == MediaCastMode::LOCAL_FILE) {
GURL url = media_router_file_dialog_->GetLastSelectedFileUrl();
tab_contents = OpenTabWithUrl(url);
params = GetLocalFileRouteParameters(sink_id, url, tab_contents);
} else {
params = GetRouteParameters(sink_id, cast_mode);
}
if (!params) {
SendIssueForUnableToCast(cast_mode);
return false;
}
GetIssueManager()->ClearNonBlockingIssues();
GetMediaRouter()->CreateRoute(params->source_id, sink_id, params->origin,
tab_contents,
std::move(params->route_response_callbacks),
params->timeout, params->incognito);
return true;
}
base::Optional<RouteParameters> MediaRouterUI::GetLocalFileRouteParameters(
const MediaSink::Id& sink_id,
const GURL& file_url,
content::WebContents* tab_contents) {
RouteParameters params;
SessionID::id_type tab_id = SessionTabHelper::IdForTab(tab_contents).id();
params.source_id = MediaSourceForTab(tab_id).id();
// Use a placeholder URL as origin for local file casting, which is
// essentially mirroring.
params.origin = url::Origin::Create(GURL(chrome::kChromeUIMediaRouterURL));
params.route_response_callbacks.push_back(base::BindOnce(
&MediaRouterUI::OnRouteResponseReceived, weak_factory_.GetWeakPtr(),
current_route_request_id(), sink_id, MediaCastMode::LOCAL_FILE,
base::UTF8ToUTF16(GetTruncatedPresentationRequestSourceName())));
params.route_response_callbacks.push_back(
base::BindOnce(&MediaRouterUIBase::MaybeReportCastingSource,
weak_factory_.GetWeakPtr(), MediaCastMode::LOCAL_FILE));
params.route_response_callbacks.push_back(base::BindOnce(
&MediaRouterUI::MaybeReportFileInformation, weak_factory_.GetWeakPtr()));
params.route_response_callbacks.push_back(
base::BindOnce(&MediaRouterUI::FullScreenFirstVideoElement,
weak_factory_.GetWeakPtr(), file_url, tab_contents));
params.timeout = GetRouteRequestTimeout(MediaCastMode::LOCAL_FILE);
CHECK(initiator());
params.incognito = initiator()->GetBrowserContext()->IsOffTheRecord();
return base::make_optional(std::move(params));
}
bool MediaRouterUI::ConnectRoute(const MediaSink::Id& sink_id,
const MediaRoute::Id& route_id) {
base::Optional<RouteParameters> params =
GetRouteParameters(sink_id, MediaCastMode::PRESENTATION);
if (!params) {
SendIssueForUnableToCast(MediaCastMode::PRESENTATION);
return false;
}
GetIssueManager()->ClearNonBlockingIssues();
GetMediaRouter()->ConnectRouteByRouteId(
params->source_id, route_id, params->origin, initiator(),
std::move(params->route_response_callbacks), params->timeout,
params->incognito);
return true;
}
void MediaRouterUI::AddIssue(const IssueInfo& issue) {
GetIssueManager()->AddIssue(issue);
}
void MediaRouterUI::ClearIssue(const Issue::Id& issue_id) {
GetIssueManager()->ClearIssue(issue_id);
}
void MediaRouterUI::OpenFileDialog() {
if (!media_router_file_dialog_) {
media_router_file_dialog_ = std::make_unique<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().
GetMediaRouter()->SearchSinks(
sink_id, source_id, search_criteria, domain,
base::BindRepeating(&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(
std::make_unique<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::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) {
MediaRouterUIBase::OnRoutesUpdated(routes, joinable_route_ids);
joinable_route_ids_.clear();
for (const MediaRoute& route : routes) {
if (route.for_display() &&
base::ContainsValue(joinable_route_ids, route.media_route_id())) {
joinable_route_ids_.push_back(route.media_route_id());
}
}
if (ui_initialized_) {
handler_->UpdateRoutes(MediaRouterUIBase::routes(), joinable_route_ids_,
routes_and_cast_modes());
}
UpdateRoutesToCastModesMapping();
}
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) {
MediaRouterUIBase::OnRouteResponseReceived(
route_request_id, sink_id, cast_mode, presentation_request_source_name,
result);
handler_->OnCreateRouteResponseReceived(sink_id, result.route());
if (result.result_code() == RouteRequestResult::TIMED_OUT)
SendIssueForRouteTimeout(cast_mode, presentation_request_source_name);
}
// TODO(crbug.com/792547): Refactor these next two methods into a local media
// casting specific location instead of here in the main ui.
void MediaRouterUI::FullScreenFirstVideoElement(
const GURL& file_url,
content::WebContents* web_contents,
const RouteRequestResult& result) {
if (result.result_code() == RouteRequestResult::OK) {
new WebContentsFullscreenOnLoadedObserver(file_url, web_contents);
}
}
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);
CreateRoute(found_sink_id, cast_mode);
}
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));
}
std::string MediaRouterUI::GetPresentationRequestSourceName() const {
GURL gurl = GetFrameURL();
return gurl.SchemeIs(extensions::kExtensionScheme)
? GetExtensionName(gurl, extensions::ExtensionRegistry::Get(
Profile::FromWebUI(web_ui())))
: GetHostFromURL(gurl);
}
const std::set<MediaCastMode>& MediaRouterUI::cast_modes() const {
return cast_modes_;
}
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) {
if (ui_initialized_) {
handler_->UpdateMaxDialogHeight(height);
}
}
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 =
GetMediaRouter()->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_ =
std::make_unique<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::Create(initiator()->GetLastCommittedURL())
: url::Origin();
return origin.Serialize();
}
IssueManager* MediaRouterUI::GetIssueManager() {
return GetMediaRouter()->GetIssueManager();
}
void MediaRouterUI::UpdateSinks() {
if (ui_initialized_)
handler_->UpdateSinks(GetEnabledSinks());
}
MediaRouter* MediaRouterUI::GetMediaRouter() const {
return MediaRouterFactory::GetApiForBrowserContext(
web_ui()->GetWebContents()->GetBrowserContext());
}
} // namespace media_router