0

Add MRUIBase and MRViewsUI

- Factor out parts of MediaRouterUI that will be shared with the Views
  implementation of the Cast dialog into MRUIBase
- Add a stub MRViewsUI class, which (in a future patch) will be
  responsible for notifying the Views dialog of sink/route updates
- Have MRUI and MRViewsUI subclass MRUIBase

Bug: 826091
Change-Id: Id162dd1ab7b0722489f9a61f680e4f5e4de86a36
Reviewed-on: https://chromium-review.googlesource.com/994275
Commit-Queue: Takumi Fujimoto <takumif@chromium.org>
Reviewed-by: mark a. foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#554980}
This commit is contained in:
Takumi Fujimoto
2018-05-01 02:52:24 +00:00
committed by Commit Bot
parent 129345b66a
commit 72d48c21cd
13 changed files with 1087 additions and 778 deletions

@@ -1365,6 +1365,10 @@ split_static_library("ui") {
"media_router/media_router_dialog_controller_impl_base.h",
"media_router/media_router_file_dialog.cc",
"media_router/media_router_file_dialog.h",
"media_router/media_router_ui_base.cc",
"media_router/media_router_ui_base.h",
"media_router/media_router_ui_helper.cc",
"media_router/media_router_ui_helper.h",
"media_router/media_sink_with_cast_modes.cc",
"media_router/media_sink_with_cast_modes.h",
"media_router/presentation_receiver_window.h",
@@ -3152,6 +3156,8 @@ split_static_library("ui") {
"views/media_router/cast_dialog_sink_button.h",
"views/media_router/media_router_dialog_controller_views.cc",
"views/media_router/media_router_dialog_controller_views.h",
"views/media_router/media_router_views_ui.cc",
"views/media_router/media_router_views_ui.h",
"views/media_router/presentation_receiver_window_factory.cc",
"views/media_router/presentation_receiver_window_frame.cc",
"views/media_router/presentation_receiver_window_frame.h",

@@ -0,0 +1,453 @@
// Copyright 2018 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/media_router/media_router_ui_base.h"
#include <algorithm>
#include <string>
#include <unordered_map>
#include <utility>
#include "base/macros.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.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/presentation/presentation_service_delegate_impl.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.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 "extensions/browser/extension_registry.h"
#include "extensions/common/constants.h"
#include "third_party/icu/source/i18n/unicode/coll.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 {
namespace {
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;
}
// Returns the first source in |sources| that can be connected to, 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(), IsCastPresentationUrl);
return source_it != sources.end() ? *source_it : MediaSource("");
}
} // namespace
MediaRouterUIBase::UIMediaRoutesObserver::UIMediaRoutesObserver(
MediaRouter* router,
const MediaSource::Id& source_id,
const RoutesUpdatedCallback& callback)
: MediaRoutesObserver(router, source_id), callback_(callback) {
DCHECK(!callback_.is_null());
}
MediaRouterUIBase::UIMediaRoutesObserver::~UIMediaRoutesObserver() {}
void MediaRouterUIBase::UIMediaRoutesObserver::OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) {
callback_.Run(routes, joinable_route_ids);
}
MediaRouterUIBase::MediaRouterUIBase()
: current_route_request_id_(-1),
route_request_counter_(0),
initiator_(nullptr),
weak_factory_(this) {}
MediaRouterUIBase::~MediaRouterUIBase() {
if (query_result_manager_.get())
query_result_manager_->RemoveObserver(this);
if (presentation_service_delegate_.get())
presentation_service_delegate_->RemoveDefaultPresentationRequestObserver(
this);
// If |start_presentation_context_| still exists, then it means presentation
// route request was never attempted.
if (start_presentation_context_) {
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) {
start_presentation_context_->InvokeErrorCallback(
blink::mojom::PresentationError(blink::mojom::PresentationErrorType::
PRESENTATION_REQUEST_CANCELLED,
"Dialog closed."));
} else {
start_presentation_context_->InvokeErrorCallback(
blink::mojom::PresentationError(
blink::mojom::PresentationErrorType::NO_AVAILABLE_SCREENS,
"No screens found."));
}
}
}
void MediaRouterUIBase::InitWithDefaultMediaSource(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate) {
DCHECK(initiator);
DCHECK(!presentation_service_delegate_);
DCHECK(!query_result_manager_);
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_ = std::make_unique<UIMediaRoutesObserver>(
GetMediaRouter(), MediaSource::Id(),
base::BindRepeating(&MediaRouterUIBase::OnRoutesUpdated,
base::Unretained(this)));
}
}
void MediaRouterUIBase::InitWithStartPresentationContext(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate,
std::unique_ptr<StartPresentationContext> context) {
DCHECK(initiator);
DCHECK(delegate);
DCHECK(context);
DCHECK(!start_presentation_context_);
DCHECK(!query_result_manager_);
start_presentation_context_ = std::move(context);
presentation_service_delegate_ = delegate->GetWeakPtr();
InitCommon(initiator);
OnDefaultPresentationChanged(
start_presentation_context_->presentation_request());
}
std::vector<MediaSource> MediaRouterUIBase::GetSourcesForCastMode(
MediaCastMode cast_mode) const {
return query_result_manager_->GetSourcesForCastMode(cast_mode);
}
void MediaRouterUIBase::HandleCreateSessionRequestRouteResponse(
const RouteRequestResult&) {}
void MediaRouterUIBase::InitCommon(content::WebContents* initiator) {
DCHECK(initiator);
initiator_ = initiator;
GetMediaRouter()->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_ =
std::make_unique<QueryResultManager>(GetMediaRouter());
query_result_manager_->AddObserver(this);
// Use a placeholder URL as origin for mirroring.
url::Origin origin = url::Origin::Create(GURL());
// 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);
}
SessionID::id_type tab_id = SessionTabHelper::IdForTab(initiator).id();
if (tab_id != -1) {
MediaSource mirroring_source(MediaSourceForTab(tab_id));
query_result_manager_->SetSourcesForCastMode(MediaCastMode::TAB_MIRROR,
{mirroring_source}, origin);
}
// Get the current list of media routes, so that the WebUI will have routes
// information at initialization.
OnRoutesUpdated(GetMediaRouter()->GetCurrentRoutes(),
std::vector<MediaRoute::Id>());
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
display_observer_ = WebContentsDisplayObserver::Create(
initiator_, base::BindRepeating(&MediaRouterUIBase::UpdateSinks,
base::Unretained(this)));
#endif
}
void MediaRouterUIBase::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_ = std::make_unique<UIMediaRoutesObserver>(
GetMediaRouter(), source_for_route_observer.id(),
base::BindRepeating(&MediaRouterUIBase::OnRoutesUpdated,
base::Unretained(this)));
}
void MediaRouterUIBase::OnDefaultPresentationRemoved() {
presentation_request_.reset();
query_result_manager_->RemoveSourcesForCastMode(MediaCastMode::PRESENTATION);
// Register for MediaRoute updates without a media source.
routes_observer_ = std::make_unique<UIMediaRoutesObserver>(
GetMediaRouter(), MediaSource::Id(),
base::BindRepeating(&MediaRouterUIBase::OnRoutesUpdated,
base::Unretained(this)));
}
base::Optional<RouteParameters> MediaRouterUIBase::GetRouteParameters(
const MediaSink::Id& sink_id,
MediaCastMode cast_mode) {
DCHECK(query_result_manager_);
DCHECK(initiator_);
RouteParameters params;
// 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 base::nullopt;
}
params.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 base::nullopt;
}
current_route_request_id_ = ++route_request_counter_;
params.origin = for_presentation_source ? presentation_request_->frame_origin
: url::Origin::Create(GURL());
DVLOG(1) << "DoCreateRoute: origin: " << params.origin;
// There are 3 cases. In cases (1) and (3) the MediaRouterUIBase 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 PresentationRequest.start() call.
// The StartPresentationContext 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 || !start_presentation_context_) {
params.route_response_callbacks.push_back(base::BindOnce(
&MediaRouterUIBase::OnRouteResponseReceived, weak_factory_.GetWeakPtr(),
current_route_request_id_, sink_id, cast_mode,
base::UTF8ToUTF16(GetTruncatedPresentationRequestSourceName())));
}
if (for_presentation_source) {
if (start_presentation_context_) {
// |start_presentation_context_| will be nullptr after this call, as the
// object will be transferred to the callback.
params.route_response_callbacks.push_back(
base::BindOnce(&StartPresentationContext::HandleRouteResponse,
std::move(start_presentation_context_)));
params.route_response_callbacks.push_back(base::BindOnce(
&MediaRouterUIBase::HandleCreateSessionRequestRouteResponse,
weak_factory_.GetWeakPtr()));
} else if (presentation_service_delegate_) {
params.route_response_callbacks.push_back(base::BindOnce(
&PresentationServiceDelegateImpl::OnRouteResponse,
presentation_service_delegate_, *presentation_request_));
}
}
params.route_response_callbacks.push_back(
base::BindOnce(&MediaRouterUIBase::MaybeReportCastingSource,
weak_factory_.GetWeakPtr(), cast_mode));
params.timeout = GetRouteRequestTimeout(cast_mode);
CHECK(initiator());
params.incognito = initiator()->GetBrowserContext()->IsOffTheRecord();
return params;
}
bool MediaRouterUIBase::CreateRoute(const MediaSink::Id& sink_id,
MediaCastMode cast_mode) {
base::Optional<RouteParameters> params =
GetRouteParameters(sink_id, cast_mode);
if (!params)
return false;
GetMediaRouter()->CreateRoute(params->source_id, sink_id, params->origin,
initiator_,
std::move(params->route_response_callbacks),
params->timeout, params->incognito);
return true;
}
void MediaRouterUIBase::CloseRoute(const MediaRoute::Id& route_id) {
GetMediaRouter()->TerminateRoute(route_id);
}
void MediaRouterUIBase::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);
});
UpdateSinks();
}
void MediaRouterUIBase::OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) {
routes_.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
routes_.push_back(route);
}
}
}
void MediaRouterUIBase::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();
}
current_route_request_id_ = -1;
}
void MediaRouterUIBase::MaybeReportCastingSource(
MediaCastMode cast_mode,
const RouteRequestResult& result) {
if (result.result_code() == RouteRequestResult::OK)
MediaRouterMetrics::RecordMediaRouterCastingSource(cast_mode);
}
GURL MediaRouterUIBase::GetFrameURL() const {
return presentation_request_ ? presentation_request_->frame_origin.GetURL()
: GURL();
}
std::vector<MediaSinkWithCastModes> MediaRouterUIBase::GetEnabledSinks() const {
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
if (!display_observer_)
return sinks_;
// Filter out the wired display sink for the display that the dialog is on.
// This is not the best place to do this because MRUI should not perform a
// provider-specific behavior, but we currently do not have a way to
// communicate dialog-specific information to/from the
// WiredDisplayMediaRouteProvider.
std::vector<MediaSinkWithCastModes> enabled_sinks;
const std::string display_sink_id =
WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(
display_observer_->GetCurrentDisplay());
for (const MediaSinkWithCastModes& sink : sinks_) {
if (sink.sink.id() != display_sink_id)
enabled_sinks.push_back(sink);
}
return enabled_sinks;
#else
return sinks_;
#endif
}
std::string MediaRouterUIBase::GetTruncatedPresentationRequestSourceName()
const {
GURL gurl = GetFrameURL();
CHECK(initiator());
return gurl.SchemeIs(extensions::kExtensionScheme)
? GetExtensionName(gurl, extensions::ExtensionRegistry::Get(
initiator()->GetBrowserContext()))
: TruncateHost(GetHostFromURL(gurl));
}
MediaRouter* MediaRouterUIBase::GetMediaRouter() const {
CHECK(initiator());
return MediaRouterFactory::GetApiForBrowserContext(
initiator()->GetBrowserContext());
}
} // namespace media_router

@@ -0,0 +1,276 @@
// Copyright 2018 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.
#ifndef CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_BASE_H_
#define CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_BASE_H_
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "chrome/browser/media/router/media_router_dialog_controller.h"
#include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/media_router/media_router_ui_helper.h"
#include "chrome/browser/ui/media_router/media_sink_with_cast_modes.h"
#include "chrome/browser/ui/media_router/query_result_manager.h"
#include "chrome/common/media_router/issue.h"
#include "chrome/common/media_router/media_source.h"
#include "ui/base/ui_features.h"
#include "url/gurl.h"
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
#include "chrome/browser/ui/webui/media_router/web_contents_display_observer.h"
#endif
namespace content {
struct PresentationRequest;
class WebContents;
} // namespace content
namespace U_ICU_NAMESPACE {
class Collator;
}
namespace media_router {
class MediaRoute;
class MediaRouter;
class MediaRoutesObserver;
class MediaSink;
class RouteRequestResult;
// Abstract base class for Views and WebUI implementations. Responsible for
// observing and organizing route, sink, and presentation request information,
// and executing and keeping track of route requests from the dialog to Media
// Router.
class MediaRouterUIBase : public QueryResultManager::Observer,
public PresentationServiceDelegateImpl::
DefaultPresentationRequestObserver {
public:
MediaRouterUIBase();
~MediaRouterUIBase() override;
// Initializes internal state (e.g. starts listening for MediaSinks) for
// targeting the default MediaSource (if any) of the initiator tab that owns
// |delegate|, as well as mirroring sources of that tab.
// The contents of the UI will change as the default MediaSource changes.
// If there is a default MediaSource, then PRESENTATION MediaCastMode will be
// added to |cast_modes_|.
// Init* methods can only be called once.
// |initiator|: Reference to the WebContents that initiated the dialog.
// Must not be null.
// |delegate|: PresentationServiceDelegateImpl of the initiator tab.
// Must not be null.
// TODO(imcheng): Replace use of impl with an intermediate abstract
// interface.
void InitWithDefaultMediaSource(content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate);
// Initializes internal state targeting the presentation specified in
// |context|. Also sets up mirroring sources based on |initiator|.
// This is different from InitWithDefaultMediaSource() in that it does not
// listen for default media source changes, as the UI is fixed to the source
// in |request|.
// Init* methods can only be called once.
// |initiator|: Reference to the WebContents that initiated the dialog.
// Must not be null.
// |delegate|: PresentationServiceDelegateImpl of the initiator tab.
// Must not be null.
// |context|: Context object for the PresentationRequest. This instance will
// take ownership of it. Must not be null.
void InitWithStartPresentationContext(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate,
std::unique_ptr<StartPresentationContext> context);
// Requests a route be created from the source mapped to
// |cast_mode|, to the sink given by |sink_id|.
// Returns true if a route request is successfully submitted.
// |OnRouteResponseReceived()| will be invoked when the route request
// completes.
virtual bool CreateRoute(const MediaSink::Id& sink_id,
MediaCastMode cast_mode);
// Calls MediaRouter to close the given route.
void CloseRoute(const MediaRoute::Id& route_id);
// Logs a UMA stat for the source that was cast if the result is successful.
void MaybeReportCastingSource(MediaCastMode cast_mode,
const RouteRequestResult& result);
// Returns a subset of |sinks_| that should be listed in the dialog.
std::vector<MediaSinkWithCastModes> GetEnabledSinks() const;
// Returns a source name that can be shown in the dialog.
std::string GetTruncatedPresentationRequestSourceName() const;
const std::vector<MediaRoute>& routes() const { return routes_; }
content::WebContents* initiator() const { return initiator_; }
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
// Used in tests for wired display presentations.
void set_display_observer_for_test(
std::unique_ptr<WebContentsDisplayObserver> display_observer) {
display_observer_ = std::move(display_observer);
}
#endif
protected:
std::vector<MediaSource> GetSourcesForCastMode(MediaCastMode cast_mode) const;
// QueryResultManager::Observer:
void OnResultsUpdated(
const std::vector<MediaSinkWithCastModes>& sinks) override;
// Called by |routes_observer_| when the set of active routes has changed.
virtual void OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids);
// Callback passed to MediaRouter to receive response to route creation
// requests.
virtual void OnRouteResponseReceived(
int route_request_id,
const MediaSink::Id& sink_id,
MediaCastMode cast_mode,
const base::string16& presentation_request_source_name,
const RouteRequestResult& result);
// Closes the dialog after receiving a route response when using
// |start_presentation_context_|. This prevents the dialog from trying to use
// the same presentation request again.
virtual void HandleCreateSessionRequestRouteResponse(
const RouteRequestResult&);
// Initializes the dialog with mirroring sources derived from |initiator|.
virtual void InitCommon(content::WebContents* initiator);
// PresentationServiceDelegateImpl::DefaultPresentationObserver
void OnDefaultPresentationChanged(
const content::PresentationRequest& presentation_request) override;
void OnDefaultPresentationRemoved() override;
// Called to update the dialog with the current list of of enabled sinks.
virtual void UpdateSinks() = 0;
// Populates common route-related parameters for calls to MediaRouter.
base::Optional<RouteParameters> GetRouteParameters(
const MediaSink::Id& sink_id,
MediaCastMode cast_mode);
// Returns the default PresentationRequest's frame URL if there is one.
// Otherwise returns an empty GURL.
GURL GetFrameURL() const;
int current_route_request_id() const { return current_route_request_id_; }
StartPresentationContext* start_presentation_context() const {
return start_presentation_context_.get();
}
void set_start_presentation_context_for_test(
std::unique_ptr<StartPresentationContext> start_presentation_context) {
start_presentation_context_ = std::move(start_presentation_context);
}
QueryResultManager* query_result_manager() const {
return query_result_manager_.get();
}
private:
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest,
UIMediaRoutesObserverAssignsCurrentCastModes);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest,
UIMediaRoutesObserverSkipsUnavailableCastModes);
class UIMediaRoutesObserver : public MediaRoutesObserver {
public:
using RoutesUpdatedCallback =
base::RepeatingCallback<void(const std::vector<MediaRoute>&,
const std::vector<MediaRoute::Id>&)>;
UIMediaRoutesObserver(MediaRouter* router,
const MediaSource::Id& source_id,
const RoutesUpdatedCallback& callback);
~UIMediaRoutesObserver() override;
// MediaRoutesObserver
void OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) override;
private:
// Callback to the owning MediaRouterUIBase instance.
RoutesUpdatedCallback callback_;
DISALLOW_COPY_AND_ASSIGN(UIMediaRoutesObserver);
};
// Returns the MediaRouter for this instance's BrowserContext.
virtual MediaRouter* GetMediaRouter() const;
// This is non-null while this instance is registered to receive
// updates from them.
std::unique_ptr<MediaRoutesObserver> routes_observer_;
// Set to -1 if not tracking a pending route request.
int current_route_request_id_;
// Sequential counter for route requests. Used to update
// |current_route_request_id_| when there is a new route request.
int route_request_counter_;
// Used for locale-aware sorting of sinks by name. Set during |InitCommon()|
// using the current locale.
std::unique_ptr<icu::Collator> collator_;
std::vector<MediaSinkWithCastModes> sinks_;
std::vector<MediaRoute> routes_;
// Monitors and reports sink availability.
std::unique_ptr<QueryResultManager> query_result_manager_;
// If set, then the result of the next presentation route request will
// be handled by this object.
std::unique_ptr<StartPresentationContext> start_presentation_context_;
// Set to the presentation request corresponding to the presentation cast
// mode, if supported. Otherwise set to nullopt.
base::Optional<content::PresentationRequest> presentation_request_;
// It's possible for PresentationServiceDelegateImpl to be destroyed before
// this class.
// (e.g. if a tab with the UI open is closed, then the tab WebContents will
// be destroyed first momentarily before the UI WebContents).
// Holding a WeakPtr to PresentationServiceDelegateImpl is the cleanest way to
// handle this.
// TODO(imcheng): hold a weak ptr to an abstract type instead.
base::WeakPtr<PresentationServiceDelegateImpl> presentation_service_delegate_;
// WebContents for the tab for which the Cast dialog is shown.
content::WebContents* initiator_;
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
// Keeps track of which display the initiator WebContents is on. This is used
// to make sure we don't show a wired display presentation over the
// controlling window.
std::unique_ptr<WebContentsDisplayObserver> display_observer_;
#endif
// NOTE: Weak pointers must be invalidated before all other member variables.
// Therefore |weak_factory_| must be placed at the end.
base::WeakPtrFactory<MediaRouterUIBase> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MediaRouterUIBase);
};
} // namespace media_router
#endif // CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_BASE_H_

@@ -0,0 +1,68 @@
// Copyright 2018 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/media_router/media_router_ui_helper.h"
#include "base/time/time.h"
#include "extensions/browser/extension_registry.h"
#include "url/gurl.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;
} // namespace
std::string 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();
}
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;
}
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();
}
}
RouteParameters::RouteParameters() = default;
RouteParameters::RouteParameters(RouteParameters&& other) = default;
RouteParameters::~RouteParameters() = default;
RouteParameters& RouteParameters::operator=(RouteParameters&& other) = default;
} // namespace media_router

@@ -0,0 +1,53 @@
// Copyright 2018 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.
#ifndef CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_HELPER_H_
#define CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_HELPER_H_
#include <string>
#include <vector>
#include "base/time/time.h"
#include "chrome/browser/media/router/media_router.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/common/media_router/media_source.h"
#include "url/origin.h"
namespace extensions {
class ExtensionRegistry;
}
class GURL;
namespace media_router {
// Returns the extension name for |url|, so that it can be displayed for
// extension-initiated presentations.
std::string GetExtensionName(const GURL& url,
extensions::ExtensionRegistry* registry);
std::string GetHostFromURL(const GURL& gurl);
// Returns the duration to wait for route creation result before we time out.
base::TimeDelta GetRouteRequestTimeout(MediaCastMode cast_mode);
// Contains common parameters for route requests to MediaRouter.
struct RouteParameters {
public:
RouteParameters();
RouteParameters(RouteParameters&& other);
~RouteParameters();
RouteParameters& operator=(RouteParameters&& other);
MediaSource::Id source_id;
url::Origin origin;
std::vector<MediaRouteResponseCallback> route_response_callbacks;
base::TimeDelta timeout;
bool incognito;
};
} // namespace media_router
#endif // CHROME_BROWSER_UI_MEDIA_ROUTER_MEDIA_ROUTER_UI_HELPER_H_

@@ -0,0 +1,45 @@
// Copyright 2018 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/media_router/media_router_ui_helper.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_router {
TEST(MediaRouterUIHelperTest, GetExtensionNameExtensionPresent) {
std::string id = "extensionid";
GURL url = GURL("chrome-extension://" + id);
std::unique_ptr<extensions::ExtensionRegistry> registry =
std::make_unique<extensions::ExtensionRegistry>(nullptr);
scoped_refptr<extensions::Extension> app =
extensions::ExtensionBuilder(
"test app name", extensions::ExtensionBuilder::Type::PLATFORM_APP)
.SetID(id)
.Build();
ASSERT_TRUE(registry->AddEnabled(app));
EXPECT_EQ("test app name", GetExtensionName(url, registry.get()));
}
TEST(MediaRouterUIHelperTest, GetExtensionNameEmptyWhenNotInstalled) {
std::string id = "extensionid";
GURL url = GURL("chrome-extension://" + id);
std::unique_ptr<extensions::ExtensionRegistry> registry =
std::make_unique<extensions::ExtensionRegistry>(nullptr);
EXPECT_EQ("", GetExtensionName(url, registry.get()));
}
TEST(MediaRouterUIHelperTest, GetExtensionNameEmptyWhenNotExtensionURL) {
GURL url = GURL("https://www.google.com");
std::unique_ptr<extensions::ExtensionRegistry> registry =
std::make_unique<extensions::ExtensionRegistry>(nullptr);
EXPECT_EQ("", GetExtensionName(url, registry.get()));
}
} // namespace media_router

@@ -0,0 +1,17 @@
// Copyright 2018 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/views/media_router/media_router_views_ui.h"
namespace media_router {
MediaRouterViewsUI::MediaRouterViewsUI() = default;
MediaRouterViewsUI::~MediaRouterViewsUI() = default;
void MediaRouterViewsUI::UpdateSinks() {
// TODO(crbug.com/826091): Implement this method.
}
} // namespace media_router

@@ -0,0 +1,28 @@
// Copyright 2018 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.
#ifndef CHROME_BROWSER_UI_VIEWS_MEDIA_ROUTER_MEDIA_ROUTER_VIEWS_UI_H_
#define CHROME_BROWSER_UI_VIEWS_MEDIA_ROUTER_MEDIA_ROUTER_VIEWS_UI_H_
#include "base/macros.h"
#include "chrome/browser/ui/media_router/media_router_ui_base.h"
namespace media_router {
// Functions as an intermediary between MediaRouter and Views Cast dialog.
class MediaRouterViewsUI : public MediaRouterUIBase {
public:
MediaRouterViewsUI();
~MediaRouterViewsUI() override;
private:
// MediaRouterUIBase:
void UpdateSinks() override;
DISALLOW_COPY_AND_ASSIGN(MediaRouterViewsUI);
};
} // namespace media_router
#endif // CHROME_BROWSER_UI_VIEWS_MEDIA_ROUTER_MEDIA_ROUTER_VIEWS_UI_H_

@@ -15,22 +15,19 @@
#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/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_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/presentation/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/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"
@@ -40,7 +37,6 @@
#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"
@@ -56,7 +52,6 @@
#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 "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"
@@ -68,85 +63,19 @@
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(), IsCastPresentationUrl);
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() {
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) {
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_;
initiator()->GetController().LoadURLWithParams(load_params);
return initiator();
} else {
return chrome::AddSelectedTabWithURL(GetBrowser(), url,
ui::PAGE_TRANSITION_LINK);
@@ -291,22 +220,6 @@ class MediaRouterUI::WebContentsFullscreenOnLoadedObserver final
}
};
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)
@@ -330,10 +243,6 @@ void MediaRouterUI::UIMediaRouteControllerObserver::OnControllerInvalidated() {
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 = std::make_unique<MediaRouterWebUIMessageHandler>(this);
handler_ = handler.get();
@@ -352,138 +261,15 @@ MediaRouterUI::MediaRouterUI(content::WebUI* web_ui)
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 |start_presentation_context_| still exists, then it means presentation
// route request was never attempted.
if (start_presentation_context_) {
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) {
start_presentation_context_->InvokeErrorCallback(
blink::mojom::PresentationError(blink::mojom::PresentationErrorType::
PRESENTATION_REQUEST_CANCELLED,
"Dialog closed."));
} else {
start_presentation_context_->InvokeErrorCallback(
blink::mojom::PresentationError(
blink::mojom::PresentationErrorType::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_ = std::make_unique<UIMediaRoutesObserver>(
router_, MediaSource::Id(),
base::BindRepeating(&MediaRouterUI::OnRoutesUpdated,
base::Unretained(this)));
}
}
void MediaRouterUI::InitWithStartPresentationContext(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate,
std::unique_ptr<StartPresentationContext> context) {
DCHECK(initiator);
DCHECK(delegate);
DCHECK(context);
DCHECK(!start_presentation_context_);
DCHECK(!query_result_manager_);
start_presentation_context_ = std::move(context);
presentation_service_delegate_ = delegate->GetWeakPtr();
InitCommon(initiator);
OnDefaultPresentationChanged(
start_presentation_context_->presentation_request());
}
MediaRouterUI::~MediaRouterUI() = default;
void MediaRouterUI::InitCommon(content::WebContents* initiator) {
DCHECK(initiator);
TRACE_EVENT_NESTABLE_ASYNC_INSTANT1("media_router", "UI", initiator,
"MediaRouterUI::InitCommon", this);
router_ = GetMediaRouter();
DCHECK(router_);
MediaRouterUIBase::InitCommon(initiator);
UpdateCastModes();
// Presentation requests from content must show the origin requesting
// presentation: crbug.com/704964
if (start_presentation_context_)
if (start_presentation_context())
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_ = std::make_unique<QueryResultManager>(router_);
query_result_manager_->AddObserver(this);
// Use a placeholder URL as origin for mirroring.
url::Origin origin =
url::Origin::Create(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 tab_id = SessionTabHelper::IdForTab(initiator);
if (tab_id.is_valid()) {
MediaSource mirroring_source(MediaSourceForTab(tab_id.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>());
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
display_observer_ = WebContentsDisplayObserver::Create(
initiator_,
base::BindRepeating(&MediaRouterUI::UpdateSinks, base::Unretained(this)));
#endif
}
void MediaRouterUI::InitForTest(
@@ -493,12 +279,12 @@ void MediaRouterUI::InitForTest(
std::unique_ptr<StartPresentationContext> context,
std::unique_ptr<MediaRouterFileDialog> file_dialog) {
handler_ = handler;
start_presentation_context_ = std::move(context);
set_start_presentation_context_for_test(std::move(context));
InitForTest(std::move(file_dialog));
InitCommon(initiator);
if (start_presentation_context_) {
if (start_presentation_context()) {
OnDefaultPresentationChanged(
start_presentation_context_->presentation_request());
start_presentation_context()->presentation_request());
}
UIInitialized();
@@ -511,64 +297,39 @@ void MediaRouterUI::InitForTest(
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_ = std::make_unique<UIMediaRoutesObserver>(
router_, source_for_route_observer.id(),
base::BindRepeating(&MediaRouterUI::OnRoutesUpdated,
base::Unretained(this)));
MediaRouterUIBase::OnDefaultPresentationChanged(presentation_request);
UpdateCastModes();
}
void MediaRouterUI::OnDefaultPresentationRemoved() {
presentation_request_.reset();
query_result_manager_->RemoveSourcesForCastMode(MediaCastMode::PRESENTATION);
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;
// Register for MediaRoute updates without a media source.
routes_observer_ = std::make_unique<UIMediaRoutesObserver>(
router_, MediaSource::Id(),
base::BindRepeating(&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();
// 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(),
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)) {
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_) {
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(
@@ -586,202 +347,96 @@ void MediaRouterUI::Close() {
}
void MediaRouterUI::UIInitialized() {
TRACE_EVENT_NESTABLE_ASYNC_END0("media_router", "UI", initiator_);
ui_initialized_ = true;
TRACE_EVENT_NESTABLE_ASYNC_END0("media_router", "UI", initiator());
// Workaround for MediaRouterElementsBrowserTest, in which MediaRouterUI is
// created without calling one of the |Init*()| methods.
// TODO(imcheng): We should be able to instantiate |issue_observer_| during
// InitCommon by storing an initial Issue in this class.
if (!router_)
router_ = GetMediaRouter();
// 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) {
MediaSource::Id source_id;
url::Origin origin;
std::vector<MediaRouteResponseCallback> route_response_callbacks;
base::TimeDelta timeout;
bool incognito;
// Default the tab casting the content to the initiator, and change if
// necessary.
content::WebContents* tab_contents = initiator_;
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);
SessionID tab_id = SessionTabHelper::IdForTab(tab_contents);
source_id = MediaSourceForTab(tab_id.id()).id();
SetLocalFileRouteParameters(sink_id, &origin, url, tab_contents,
&route_response_callbacks, &timeout,
&incognito);
} else if (!SetRouteParameters(sink_id, cast_mode, &source_id, &origin,
&route_response_callbacks, &timeout,
&incognito)) {
params = GetLocalFileRouteParameters(sink_id, url, tab_contents);
} else {
params = GetRouteParameters(sink_id, cast_mode);
}
if (!params) {
SendIssueForUnableToCast(cast_mode);
return false;
}
GetIssueManager()->ClearNonBlockingIssues();
router_->CreateRoute(source_id, sink_id, origin, tab_contents,
std::move(route_response_callbacks), timeout, incognito);
GetMediaRouter()->CreateRoute(params->source_id, sink_id, params->origin,
tab_contents,
std::move(params->route_response_callbacks),
params->timeout, params->incognito);
return true;
}
bool MediaRouterUI::SetRouteParameters(
base::Optional<RouteParameters> MediaRouterUI::GetLocalFileRouteParameters(
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::Create(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 PresentationRequest.start() call.
// The StartPresentationContext 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 || !start_presentation_context_) {
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 (start_presentation_context_) {
// |start_presentation_context_| will be nullptr after this call, as the
// object will be transferred to the callback.
route_response_callbacks->push_back(
base::BindOnce(&StartPresentationContext::HandleRouteResponse,
std::move(start_presentation_context_)));
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));
*timeout = GetRouteRequestTimeout(cast_mode);
*incognito = Profile::FromWebUI(web_ui())->IsOffTheRecord();
return true;
}
// TODO(crbug.com/751317): This function and the above function are messy, this
// code would be much neater if the route params were combined in a single
// struct, which will require mojo changes as well.
bool MediaRouterUI::SetLocalFileRouteParameters(
const MediaSink::Id& sink_id,
url::Origin* origin,
const GURL& file_url,
content::WebContents* tab_contents,
std::vector<MediaRouteResponseCallback>* route_response_callbacks,
base::TimeDelta* timeout,
bool* incognito) {
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.
*origin = url::Origin::Create(GURL(chrome::kChromeUIMediaRouterURL));
params.origin = url::Origin::Create(GURL(chrome::kChromeUIMediaRouterURL));
route_response_callbacks->push_back(base::BindOnce(
params.route_response_callbacks.push_back(base::BindOnce(
&MediaRouterUI::OnRouteResponseReceived, weak_factory_.GetWeakPtr(),
current_route_request_id_, sink_id, MediaCastMode::LOCAL_FILE,
current_route_request_id(), sink_id, MediaCastMode::LOCAL_FILE,
base::UTF8ToUTF16(GetTruncatedPresentationRequestSourceName())));
route_response_callbacks->push_back(
base::BindOnce(&MediaRouterUI::MaybeReportCastingSource,
params.route_response_callbacks.push_back(
base::BindOnce(&MediaRouterUIBase::MaybeReportCastingSource,
weak_factory_.GetWeakPtr(), MediaCastMode::LOCAL_FILE));
route_response_callbacks->push_back(base::BindOnce(
params.route_response_callbacks.push_back(base::BindOnce(
&MediaRouterUI::MaybeReportFileInformation, weak_factory_.GetWeakPtr()));
route_response_callbacks->push_back(
params.route_response_callbacks.push_back(
base::BindOnce(&MediaRouterUI::FullScreenFirstVideoElement,
weak_factory_.GetWeakPtr(), file_url, tab_contents));
*timeout = GetRouteRequestTimeout(MediaCastMode::LOCAL_FILE);
*incognito = Profile::FromWebUI(web_ui())->IsOffTheRecord();
params.timeout = GetRouteRequestTimeout(MediaCastMode::LOCAL_FILE);
CHECK(initiator());
params.incognito = initiator()->GetBrowserContext()->IsOffTheRecord();
return true;
return params;
}
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)) {
base::Optional<RouteParameters> params =
GetRouteParameters(sink_id, MediaCastMode::PRESENTATION);
if (!params) {
SendIssueForUnableToCast(MediaCastMode::PRESENTATION);
return false;
}
GetIssueManager()->ClearNonBlockingIssues();
router_->ConnectRouteByRouteId(source_id, route_id, origin, initiator_,
std::move(route_response_callbacks), timeout,
incognito);
GetMediaRouter()->ConnectRouteByRouteId(
params->source_id, route_id, params->origin, initiator(),
std::move(params->route_response_callbacks), params->timeout,
params->incognito);
return true;
}
void MediaRouterUI::CloseRoute(const MediaRoute::Id& route_id) {
router_->TerminateRoute(route_id);
}
void MediaRouterUI::AddIssue(const IssueInfo& issue) {
GetIssueManager()->AddIssue(issue);
}
@@ -804,12 +459,12 @@ void MediaRouterUI::SearchSinksAndCreateRoute(
const std::string& domain,
MediaCastMode cast_mode) {
std::unique_ptr<MediaSource> source =
query_result_manager_->GetSourceForCastModeAndSink(cast_mode, sink_id);
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(
GetMediaRouter()->SearchSinks(
sink_id, source_id, search_criteria, domain,
base::BindRepeating(&MediaRouterUI::OnSearchSinkResponseReceived,
weak_factory_.GetWeakPtr(), cast_mode));
@@ -848,21 +503,6 @@ void MediaRouterUI::RecordCastModeSelection(MediaCastMode cast_mode) {
}
}
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_)
UpdateSinks();
}
void MediaRouterUI::SetIssue(const Issue& issue) {
if (ui_initialized_)
handler_->UpdateIssue(issue);
@@ -876,33 +516,21 @@ void MediaRouterUI::ClearIssue() {
void MediaRouterUI::OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) {
routes_.clear();
MediaRouterUIBase::OnRoutesUpdated(routes, joinable_route_ids);
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);
if (route.for_display() &&
base::ContainsValue(joinable_route_ids, route.media_route_id())) {
joinable_route_ids_.push_back(route.media_route_id());
}
}
UpdateRoutesToCastModesMapping();
if (ui_initialized_)
handler_->UpdateRoutes(routes_, joinable_route_ids_,
routes_and_cast_modes_);
if (ui_initialized_) {
handler_->UpdateRoutes(MediaRouterUIBase::routes(), joinable_route_ids_,
routes_and_cast_modes());
}
UpdateRoutesToCastModesMapping();
}
void MediaRouterUI::OnRouteResponseReceived(
@@ -911,30 +539,14 @@ void MediaRouterUI::OnRouteResponseReceived(
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;
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);
}
void MediaRouterUI::MaybeReportCastingSource(MediaCastMode cast_mode,
const RouteRequestResult& result) {
if (result.result_code() == RouteRequestResult::OK)
MediaRouterMetrics::RecordMediaRouterCastingSource(cast_mode);
}
// 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(
@@ -1007,35 +619,6 @@ void MediaRouterUI::SendIssueForUnableToCast(MediaCastMode cast_mode) {
IssueInfo::Severity::WARNING));
}
GURL MediaRouterUI::GetFrameURL() const {
return presentation_request_ ? presentation_request_->frame_origin.GetURL()
: GURL();
}
std::vector<MediaSinkWithCastModes> MediaRouterUI::GetEnabledSinks() const {
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
if (!display_observer_)
return sinks_;
// Filter out the wired display sink for the display that the dialog is on.
// This is not the best place to do this because MRUI should not perform a
// provider-specific behavior, but we currently do not have a way to
// communicate dialog-specific information to/from the
// WiredDisplayMediaRouteProvider.
std::vector<MediaSinkWithCastModes> enabled_sinks;
const std::string display_sink_id =
WiredDisplayMediaRouteProvider::GetSinkIdForDisplay(
display_observer_->GetCurrentDisplay());
for (const MediaSinkWithCastModes& sink : sinks_) {
if (sink.sink.id() != display_sink_id)
enabled_sinks.push_back(sink);
}
return enabled_sinks;
#else
return sinks_;
#endif
}
std::string MediaRouterUI::GetPresentationRequestSourceName() const {
GURL gurl = GetFrameURL();
return gurl.SchemeIs(extensions::kExtensionScheme)
@@ -1044,14 +627,6 @@ std::string MediaRouterUI::GetPresentationRequestSourceName() const {
: 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_;
}
@@ -1091,7 +666,7 @@ MediaRouteController* MediaRouterUI::GetMediaRouteController() const {
void MediaRouterUI::OnMediaControllerUIAvailable(
const MediaRoute::Id& route_id) {
scoped_refptr<MediaRouteController> controller =
router_->GetRouteController(route_id);
GetMediaRouter()->GetRouteController(route_id);
if (!controller) {
DVLOG(1) << "Requested a route controller with an invalid route ID.";
return;
@@ -1110,28 +685,29 @@ 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();
initiator() ? url::Origin::Create(initiator()->GetLastCommittedURL())
: url::Origin();
return origin.Serialize();
}
IssueManager* MediaRouterUI::GetIssueManager() {
return router_->GetIssueManager();
return GetMediaRouter()->GetIssueManager();
}
void MediaRouterUI::UpdateSinks() {
handler_->UpdateSinks(GetEnabledSinks());
if (ui_initialized_)
handler_->UpdateSinks(GetEnabledSinks());
}
MediaRouter* MediaRouterUI::GetMediaRouter() {
MediaRouter* MediaRouterUI::GetMediaRouter() const {
return MediaRouterFactory::GetApiForBrowserContext(
web_ui()->GetWebContents()->GetBrowserContext());
}
} // namespace media_router

@@ -5,129 +5,53 @@
#ifndef CHROME_BROWSER_UI_WEBUI_MEDIA_ROUTER_MEDIA_ROUTER_UI_H_
#define CHROME_BROWSER_UI_WEBUI_MEDIA_ROUTER_MEDIA_ROUTER_UI_H_
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/media/router/media_router_dialog_controller.h"
#include "base/strings/string16.h"
#include "chrome/browser/media/router/mojo/media_route_controller.h"
#include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h"
#include "chrome/browser/ui/media_router/media_cast_mode.h"
#include "chrome/browser/ui/media_router/media_router_file_dialog.h"
#include "chrome/browser/ui/media_router/media_sink_with_cast_modes.h"
#include "chrome/browser/ui/media_router/query_result_manager.h"
#include "chrome/browser/ui/media_router/media_router_ui_base.h"
#include "chrome/browser/ui/webui/constrained_web_dialog_ui.h"
#include "chrome/common/media_router/issue.h"
#include "chrome/common/media_router/media_source.h"
#include "content/public/browser/web_ui_data_source.h"
#include "third_party/icu/source/common/unicode/uversion.h"
#include "ui/base/ui_features.h"
#include "url/gurl.h"
#include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/media_sink.h"
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
#include "chrome/browser/ui/webui/media_router/web_contents_display_observer.h"
#endif
namespace content {
struct PresentationRequest;
class WebContents;
namespace ui {
struct SelectedFileInfo;
}
namespace extensions {
class ExtensionRegistry;
}
namespace U_ICU_NAMESPACE {
class Collator;
}
class Browser;
namespace media_router {
struct MediaStatus;
class Issue;
class IssueManager;
class IssuesObserver;
class MediaRoute;
class MediaRouter;
class MediaRoutesObserver;
class MediaRouterWebUIMessageHandler;
class MediaSink;
class RouteRequestResult;
// Implements the chrome://media-router user interface.
// Functions as an intermediary between MediaRouter and WebUI Cast dialog.
class MediaRouterUI
: public ConstrainedWebDialogUI,
public QueryResultManager::Observer,
public PresentationServiceDelegateImpl::
DefaultPresentationRequestObserver,
: public MediaRouterUIBase,
public ConstrainedWebDialogUI,
public MediaRouterFileDialog::MediaRouterFileDialogDelegate {
public:
// |web_ui| owns this object and is used to initialize the base class.
explicit MediaRouterUI(content::WebUI* web_ui);
~MediaRouterUI() override;
// Initializes internal state (e.g. starts listening for MediaSinks) for
// targeting the default MediaSource (if any) of the initiator tab that owns
// |initiator|: Reference to the WebContents that initiated the dialog.
// Must not be null.
// |delegate|, as well as mirroring sources of that tab.
// The contents of the UI will change as the default MediaSource changes.
// If there is a default MediaSource, then PRESENTATION MediaCastMode will be
// added to |cast_modes_|.
// Init* methods can only be called once.
// |delegate|: PresentationServiceDelegateImpl of the initiator tab.
// Must not be null.
// TODO(imcheng): Replace use of impl with an intermediate abstract
// interface.
void InitWithDefaultMediaSource(content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate);
// Initializes internal state targeting the presentation specified in
// |context|. Also sets up mirroring sources based on |initiator|.
// This is different from |InitWithDefaultMediaSource| in that it does not
// listen for default media source changes, as the UI is fixed to the source
// in |request|.
// Init* methods can only be called once.
// |initiator|: Reference to the WebContents that initiated the dialog.
// Must not be null.
// |delegate|: PresentationServiceDelegateImpl of the initiator tab.
// Must not be null.
// |context|: Context object for the PresentationRequest. This instance will
// take
// ownership of it. Must not be null.
void InitWithStartPresentationContext(
content::WebContents* initiator,
PresentationServiceDelegateImpl* delegate,
std::unique_ptr<StartPresentationContext> context);
// Closes the media router UI.
void Close();
// Notifies this instance that the UI has been initialized.
virtual void UIInitialized();
// Requests a route be created from the source mapped to
// |cast_mode|, to the sink given by |sink_id|.
// Returns true if a route request is successfully submitted.
// |OnRouteResponseReceived()| will be invoked when the route request
// completes.
bool CreateRoute(const MediaSink::Id& sink_id, MediaCastMode cast_mode);
// MediaRouterUIBase:
bool CreateRoute(const MediaSink::Id& sink_id,
MediaCastMode cast_mode) override;
// Calls MediaRouter to join the given route.
bool ConnectRoute(const MediaSink::Id& sink_id,
const MediaRoute::Id& route_id);
// Calls MediaRouter to close the given route.
void CloseRoute(const MediaRoute::Id& route_id);
// Calls MediaRouter to add the given issue.
void AddIssue(const IssueInfo& issue);
@@ -153,16 +77,11 @@ class MediaRouterUI
// mode is MediaCastMode::DESKTOP_MIRROR.
virtual void RecordCastModeSelection(MediaCastMode cast_mode);
// Returns a subset of |sinks_| that should be listed in the dialog.
std::vector<MediaSinkWithCastModes> GetEnabledSinks() const;
// Returns the hostname of the PresentationRequest's parent frame URL.
std::string GetPresentationRequestSourceName() const;
std::string GetTruncatedPresentationRequestSourceName() const;
virtual std::string GetPresentationRequestSourceName() const;
bool HasPendingRouteRequest() const {
return current_route_request_id_ != -1;
return current_route_request_id() != -1;
}
const std::vector<MediaRoute>& routes() const { return routes_; }
const std::vector<MediaRoute::Id>& joinable_route_ids() const {
return joinable_route_ids_;
}
@@ -171,7 +90,6 @@ class MediaRouterUI
routes_and_cast_modes() const {
return routes_and_cast_modes_;
}
const content::WebContents* initiator() const { return initiator_; }
const base::Optional<MediaCastMode>& forced_cast_mode() const {
return forced_cast_mode_;
}
@@ -203,13 +121,6 @@ class MediaRouterUI
void InitForTest(std::unique_ptr<MediaRouterFileDialog> file_dialog);
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
void set_display_observer_for_test(
std::unique_ptr<WebContentsDisplayObserver> display_observer) {
display_observer_ = std::move(display_observer);
}
#endif
private:
friend class MediaRouterUITest;
@@ -228,7 +139,8 @@ class MediaRouterUI
GetExtensionNameEmptyWhenNotExtensionURL);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest,
RouteCreationTimeoutForPresentation);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, RouteRequestFromIncognito);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUIIncognitoTest,
RouteRequestFromIncognito);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, OpenAndCloseUIDetailsView);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, SendMediaCommands);
FRIEND_TEST_ALL_PREFIXES(MediaRouterUITest, SendMediaStatusUpdate);
@@ -239,28 +151,6 @@ class MediaRouterUI
class UIIssuesObserver;
class WebContentsFullscreenOnLoadedObserver;
class UIMediaRoutesObserver : public MediaRoutesObserver {
public:
using RoutesUpdatedCallback =
base::RepeatingCallback<void(const std::vector<MediaRoute>&,
const std::vector<MediaRoute::Id>&)>;
UIMediaRoutesObserver(MediaRouter* router,
const MediaSource::Id& source_id,
const RoutesUpdatedCallback& callback);
~UIMediaRoutesObserver() override;
// MediaRoutesObserver
void OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) override;
private:
// Callback to the owning MediaRouterUI instance.
RoutesUpdatedCallback callback_;
DISALLOW_COPY_AND_ASSIGN(UIMediaRoutesObserver);
};
class UIMediaRouteControllerObserver : public MediaRouteController::Observer {
public:
explicit UIMediaRouteControllerObserver(
@@ -268,7 +158,7 @@ class MediaRouterUI
scoped_refptr<MediaRouteController> controller);
~UIMediaRouteControllerObserver() override;
// MediaRouteController::Observer
// MediaRouteController::Observer:
void OnMediaStatusUpdated(const MediaStatus& status) override;
void OnControllerInvalidated() override;
@@ -278,53 +168,39 @@ class MediaRouterUI
DISALLOW_COPY_AND_ASSIGN(UIMediaRouteControllerObserver);
};
static std::string GetExtensionName(const GURL& url,
extensions::ExtensionRegistry* registry);
// Retrieves the browser associated with this UI.
Browser* GetBrowser();
// Opens the URL in a tab, returns the tab it was opened in.
content::WebContents* OpenTabWithUrl(const GURL url);
// Methods for MediaRouterFileDialogDelegate
// MediaRouterFileDialogDelegate:
void FileDialogFileSelected(const ui::SelectedFileInfo& file_info) override;
void FileDialogSelectionFailed(const IssueInfo& issue) override;
// QueryResultManager::Observer
void OnResultsUpdated(
const std::vector<MediaSinkWithCastModes>& sinks) override;
// Called by |issues_observer_| when the top issue has changed.
// If the UI is already initialized, notifies |handler_| to update the UI.
// Ignored if the UI is not yet initialized.
void SetIssue(const Issue& issue);
void ClearIssue();
// Called by |routes_observer_| when the set of active routes has changed.
void OnRoutesUpdated(const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids);
void OnRoutesUpdated(
const std::vector<MediaRoute>& routes,
const std::vector<MediaRoute::Id>& joinable_route_ids) override;
// Callback passed to MediaRouter to receive response to route creation
// requests.
void OnRouteResponseReceived(
int route_request_id,
const MediaSink::Id& sink_id,
MediaCastMode cast_mode,
const base::string16& presentation_request_source_name,
const RouteRequestResult& result);
const RouteRequestResult& result) override;
// Logs a UMA stat for the source that was cast if the result is successful.
void MaybeReportCastingSource(MediaCastMode cast_mode,
const RouteRequestResult& result);
// Sends a request to the file dialog to log UMA stats for the file that was
// cast if the result is successful.
void MaybeReportFileInformation(const RouteRequestResult& result);
// Closes the dialog after receiving a route response when using
// |start_presentation_context_|. This prevents the dialog from trying to use
// the same presentation request again.
void HandleCreateSessionRequestRouteResponse(const RouteRequestResult&);
void HandleCreateSessionRequestRouteResponse(
const RouteRequestResult&) override;
// Callback passed to MediaRouter to receive the sink ID of the sink found by
// SearchSinksAndCreateRoute().
@@ -339,35 +215,19 @@ class MediaRouterUI
// Creates and sends an issue if casting fails for any other reason.
void SendIssueForUnableToCast(MediaCastMode cast_mode);
// Initializes the dialog with mirroring sources derived from |initiator|.
void InitCommon(content::WebContents* initiator);
void InitCommon(content::WebContents* initiator) override;
// PresentationServiceDelegateImpl::DefaultPresentationObserver
// PresentationServiceDelegateImpl::DefaultPresentationObserver:
void OnDefaultPresentationChanged(
const content::PresentationRequest& presentation_request) override;
void OnDefaultPresentationRemoved() override;
// Populates common route-related parameters for CreateRoute(),
// ConnectRoute(), and SearchSinksAndCreateRoute().
bool 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);
// Populates route-related parameters for CreateRoute() when doing file
// casting.
bool SetLocalFileRouteParameters(
base::Optional<RouteParameters> GetLocalFileRouteParameters(
const MediaSink::Id& sink_id,
url::Origin* origin,
const GURL& file_url,
content::WebContents* tab_contents,
std::vector<MediaRouteResponseCallback>* route_response_callbacks,
base::TimeDelta* timeout,
bool* incognito);
content::WebContents* tab_contents);
void FullScreenFirstVideoElement(const GURL& file_url,
content::WebContents* web_contents,
@@ -381,10 +241,6 @@ class MediaRouterUI
// match the value of |routes_|.
void UpdateRoutesToCastModesMapping();
// Returns the default PresentationRequest's frame URL if there is one.
// Otherwise returns an empty GURL.
GURL GetFrameURL() const;
// Returns the serialized origin for |initiator_|, or the serialization of an
// opaque origin ("null") if |initiator_| is not set.
std::string GetSerializedInitiatorOrigin() const;
@@ -400,65 +256,23 @@ class MediaRouterUI
// Returns the IssueManager associated with |router_|.
IssueManager* GetIssueManager();
// Sends the current list of enabled sinks to |handler_|.
void UpdateSinks();
void UpdateSinks() override;
// Overridden by tests.
virtual MediaRouter* GetMediaRouter();
MediaRouter* GetMediaRouter() const override;
// Owned by the |web_ui| passed in the ctor, and guaranteed to be deleted
// only after it has deleted |this|.
MediaRouterWebUIMessageHandler* handler_ = nullptr;
// These are non-null while this instance is registered to receive
// updates from them.
std::unique_ptr<IssuesObserver> issues_observer_;
std::unique_ptr<MediaRoutesObserver> routes_observer_;
// Set to true by |handler_| when the UI has been initialized.
bool ui_initialized_;
// Set to -1 if not tracking a pending route request.
int current_route_request_id_;
// Sequential counter for route requests. Used to update
// |current_route_request_id_| when there is a new route request.
int route_request_counter_;
// Used for locale-aware sorting of sinks by name. Set during |InitCommon()|
// using the current locale. Set to null
std::unique_ptr<icu::Collator> collator_;
std::vector<MediaSinkWithCastModes> sinks_;
std::vector<MediaRoute> routes_;
std::vector<MediaRoute::Id> joinable_route_ids_;
CastModeSet cast_modes_;
std::unordered_map<MediaRoute::Id, MediaCastMode> routes_and_cast_modes_;
std::unique_ptr<QueryResultManager> query_result_manager_;
// If set, then the result of the next presentation route request will
// be handled by this object.
std::unique_ptr<StartPresentationContext> start_presentation_context_;
// Set to the presentation request corresponding to the presentation cast
// mode, if supported. Otherwise set to nullptr.
base::Optional<content::PresentationRequest> presentation_request_;
// It's possible for PresentationServiceDelegateImpl to be destroyed before
// this class.
// (e.g. if a tab with the UI open is closed, then the tab WebContents will
// be destroyed first momentarily before the UI WebContents).
// Holding a WeakPtr to PresentationServiceDelegateImpl is the cleanest way to
// handle this.
// TODO(imcheng): hold a weak ptr to an abstract type instead.
base::WeakPtr<PresentationServiceDelegateImpl> presentation_service_delegate_;
content::WebContents* initiator_;
// Pointer to the MediaRouter for this instance's BrowserContext.
MediaRouter* router_;
// The start time for UI initialization metrics timer. When a dialog has been
// been painted and initialized with initial data, this should be cleared.
base::Time start_time_;
@@ -474,13 +288,6 @@ class MediaRouterUI
// If set, a cast mode that is required to be shown first.
base::Optional<MediaCastMode> forced_cast_mode_;
#if !defined(OS_MACOSX) || BUILDFLAG(MAC_VIEWS_BROWSER)
// Keeps track of which display the dialog WebContents is on.
std::unique_ptr<WebContentsDisplayObserver> display_observer_;
#endif
// NOTE: Weak pointers must be invalidated before all other member variables.
// Therefore |weak_factory_| must be placed at the end.
base::WeakPtrFactory<MediaRouterUI> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(MediaRouterUI);

@@ -15,6 +15,7 @@
#include "chrome/browser/media/router/test/mock_media_router.h"
#include "chrome/browser/media/router/test/test_helper.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/media_router/media_router_ui_helper.h"
#include "chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h"
#include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/media_source_helper.h"
@@ -123,9 +124,9 @@ class TestMediaRouterUI : public MediaRouterUI {
: MediaRouterUI(web_ui), router_(router) {}
~TestMediaRouterUI() override = default;
MediaRouter* GetMediaRouter() override { return router_; }
private:
MediaRouter* GetMediaRouter() const override { return router_; }
MediaRouter* router_;
DISALLOW_COPY_AND_ASSIGN(TestMediaRouterUI);
};
@@ -233,6 +234,14 @@ class MediaRouterUITest : public ChromeRenderViewHostTestHarness {
base::test::ScopedFeatureList scoped_feature_list_;
};
class MediaRouterUIIncognitoTest : public MediaRouterUITest {
protected:
content::BrowserContext* GetBrowserContext() override {
return static_cast<Profile*>(MediaRouterUITest::GetBrowserContext())
->GetOffTheRecordProfile();
}
};
TEST_F(MediaRouterUITest, RouteCreationTimeoutForTab) {
CreateMediaRouterUI(profile());
std::vector<MediaRouteResponseCallback> callbacks;
@@ -336,8 +345,8 @@ TEST_F(MediaRouterUITest, RouteCreationParametersCantBeCreated) {
std::move(sink_callback).Run("foundSinkId");
}
TEST_F(MediaRouterUITest, RouteRequestFromIncognito) {
CreateMediaRouterUI(profile()->GetOffTheRecordProfile());
TEST_F(MediaRouterUIIncognitoTest, RouteRequestFromIncognito) {
CreateMediaRouterUI(profile());
media_router_ui_->OnDefaultPresentationChanged(presentation_request_);
EXPECT_CALL(mock_router_,
@@ -370,7 +379,7 @@ TEST_F(MediaRouterUITest, SortedSinks) {
// Sorted order is 2, 3, 1.
media_router_ui_->OnResultsUpdated(unsorted_sinks);
const auto& sorted_sinks = media_router_ui_->sinks_;
const auto& sorted_sinks = media_router_ui_->GetEnabledSinks();
EXPECT_EQ(sink_name2, sorted_sinks[0].sink.name());
EXPECT_EQ(sink_id3, sorted_sinks[1].sink.id());
EXPECT_EQ(sink_id1, sorted_sinks[2].sink.id());
@@ -399,7 +408,7 @@ TEST_F(MediaRouterUITest, SortSinksByIconType) {
// Sorted order is CAST, CAST_AUDIO_GROUP "A", CAST_AUDIO_GROUP "B",
// CAST_AUDIO, HANGOUT, GENERIC.
media_router_ui_->OnResultsUpdated(unsorted_sinks);
const auto& sorted_sinks = media_router_ui_->sinks_;
const auto& sorted_sinks = media_router_ui_->GetEnabledSinks();
EXPECT_EQ(sink6.sink.id(), sorted_sinks[0].sink.id());
EXPECT_EQ(sink4.sink.id(), sorted_sinks[1].sink.id());
EXPECT_EQ(sink2.sink.id(), sorted_sinks[2].sink.id());
@@ -424,11 +433,11 @@ TEST_F(MediaRouterUITest, FilterNonDisplayRoutes) {
routes.push_back(display_route_2);
media_router_ui_->OnRoutesUpdated(routes, std::vector<MediaRoute::Id>());
ASSERT_EQ(2u, media_router_ui_->routes_.size());
EXPECT_TRUE(display_route_1.Equals(media_router_ui_->routes_[0]));
EXPECT_TRUE(media_router_ui_->routes_[0].for_display());
EXPECT_TRUE(display_route_2.Equals(media_router_ui_->routes_[1]));
EXPECT_TRUE(media_router_ui_->routes_[1].for_display());
ASSERT_EQ(2u, media_router_ui_->routes().size());
EXPECT_TRUE(display_route_1.Equals(media_router_ui_->routes()[0]));
EXPECT_TRUE(media_router_ui_->routes()[0].for_display());
EXPECT_TRUE(display_route_2.Equals(media_router_ui_->routes()[1]));
EXPECT_TRUE(media_router_ui_->routes()[1].for_display());
}
TEST_F(MediaRouterUITest, FilterNonDisplayJoinableRoutes) {
@@ -452,11 +461,11 @@ TEST_F(MediaRouterUITest, FilterNonDisplayJoinableRoutes) {
joinable_route_ids.push_back("routeId3");
media_router_ui_->OnRoutesUpdated(routes, joinable_route_ids);
ASSERT_EQ(2u, media_router_ui_->joinable_route_ids_.size());
ASSERT_EQ(2u, media_router_ui_->joinable_route_ids().size());
EXPECT_EQ(display_route_1.media_route_id(),
media_router_ui_->joinable_route_ids_[0]);
media_router_ui_->joinable_route_ids()[0]);
EXPECT_EQ(display_route_2.media_route_id(),
media_router_ui_->joinable_route_ids_[1]);
media_router_ui_->joinable_route_ids()[1]);
}
TEST_F(MediaRouterUITest, UIMediaRoutesObserverAssignsCurrentCastModes) {
@@ -556,39 +565,6 @@ TEST_F(MediaRouterUITest, UIMediaRoutesObserverSkipsUnavailableCastModes) {
observer.reset();
}
TEST_F(MediaRouterUITest, GetExtensionNameExtensionPresent) {
std::string id = "extensionid";
GURL url = GURL("chrome-extension://" + id);
std::unique_ptr<extensions::ExtensionRegistry> registry =
std::make_unique<extensions::ExtensionRegistry>(nullptr);
scoped_refptr<extensions::Extension> app =
extensions::ExtensionBuilder(
"test app name", extensions::ExtensionBuilder::Type::PLATFORM_APP)
.SetID(id)
.Build();
ASSERT_TRUE(registry->AddEnabled(app));
EXPECT_EQ("test app name",
MediaRouterUI::GetExtensionName(url, registry.get()));
}
TEST_F(MediaRouterUITest, GetExtensionNameEmptyWhenNotInstalled) {
std::string id = "extensionid";
GURL url = GURL("chrome-extension://" + id);
std::unique_ptr<extensions::ExtensionRegistry> registry =
std::make_unique<extensions::ExtensionRegistry>(nullptr);
EXPECT_EQ("", MediaRouterUI::GetExtensionName(url, registry.get()));
}
TEST_F(MediaRouterUITest, GetExtensionNameEmptyWhenNotExtensionURL) {
GURL url = GURL("https://www.google.com");
std::unique_ptr<extensions::ExtensionRegistry> registry =
std::make_unique<extensions::ExtensionRegistry>(nullptr);
EXPECT_EQ("", MediaRouterUI::GetExtensionName(url, registry.get()));
}
TEST_F(MediaRouterUITest, NotFoundErrorOnCloseWithNoSinks) {
blink::mojom::PresentationError expected_error(
blink::mojom::PresentationErrorType::NO_AVAILABLE_SCREENS,

@@ -94,6 +94,7 @@ class MockMediaRouterUI : public MediaRouterUI {
MOCK_METHOD0(UIInitialized, void());
MOCK_CONST_METHOD0(UserSelectedTabMirroringForCurrentOrigin, bool());
MOCK_METHOD1(RecordCastModeSelection, void(MediaCastMode cast_mode));
MOCK_CONST_METHOD0(GetPresentationRequestSourceName, std::string());
MOCK_CONST_METHOD0(cast_modes, const std::set<MediaCastMode>&());
MOCK_METHOD1(OnMediaControllerUIAvailable,
void(const MediaRoute::Id& route_id));
@@ -555,6 +556,8 @@ TEST_F(MediaRouterWebUIMessageHandlerTest, RetrieveCastModeSelection) {
EXPECT_CALL(*mock_media_router_ui_, cast_modes())
.WillRepeatedly(ReturnRef(cast_modes));
EXPECT_CALL(*mock_media_router_ui_, GetPresentationRequestSourceName())
.WillRepeatedly(Return("source"));
EXPECT_CALL(*mock_media_router_ui_,
UserSelectedTabMirroringForCurrentOrigin())
.WillOnce(Return(true));

@@ -3137,6 +3137,7 @@ test("unit_tests") {
"../browser/ui/media_router/cast_modes_with_media_sources_unittest.cc",
"../browser/ui/media_router/media_cast_mode_unittest.cc",
"../browser/ui/media_router/media_router_file_dialog_unittest.cc",
"../browser/ui/media_router/media_router_ui_helper_unittest.cc",
"../browser/ui/media_router/query_result_manager_unittest.cc",
"../browser/ui/passwords/manage_passwords_ui_controller_unittest.cc",
"../browser/ui/toolbar/media_router_action_controller_unittest.cc",