1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/webui/media_router/media_router_ui.h"
9 #include "base/strings/string_util.h"
10 #include "chrome/browser/media/router/create_presentation_session_request.h"
11 #include "chrome/browser/media/router/issue.h"
12 #include "chrome/browser/media/router/issues_observer.h"
13 #include "chrome/browser/media/router/media_route.h"
14 #include "chrome/browser/media/router/media_router.h"
15 #include "chrome/browser/media/router/media_router_factory.h"
16 #include "chrome/browser/media/router/media_router_mojo_impl.h"
17 #include "chrome/browser/media/router/media_routes_observer.h"
18 #include "chrome/browser/media/router/media_sink.h"
19 #include "chrome/browser/media/router/media_sinks_observer.h"
20 #include "chrome/browser/media/router/media_source.h"
21 #include "chrome/browser/media/router/media_source_helper.h"
22 #include "chrome/browser/media/router/presentation_service_delegate_impl.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/sessions/session_tab_helper.h"
25 #include "chrome/browser/ui/webui/media_router/media_router_localized_strings_provider.h"
26 #include "chrome/browser/ui/webui/media_router/media_router_resources_provider.h"
27 #include "chrome/browser/ui/webui/media_router/media_router_webui_message_handler.h"
28 #include "chrome/common/url_constants.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/browser/web_ui.h"
31 #include "content/public/browser/web_ui_data_source.h"
32 #include "ui/web_dialogs/web_dialog_delegate.h"
34 namespace media_router
{
38 std::string
GetHostFromURL(const GURL
& gurl
) {
41 std::string host
= gurl
.host();
42 if (base::StartsWith(host
, "www.", base::CompareCase::INSENSITIVE_ASCII
))
43 host
= host
.substr(4);
49 // This class calls to refresh the UI when the highest priority issue is
51 class MediaRouterUI::UIIssuesObserver
: public IssuesObserver
{
53 UIIssuesObserver(MediaRouter
* router
, MediaRouterUI
* ui
)
54 : IssuesObserver(router
), ui_(ui
) {
58 ~UIIssuesObserver() override
{}
60 // IssuesObserver implementation.
61 void OnIssueUpdated(const Issue
* issue
) override
{ ui_
->SetIssue(issue
); }
64 // Reference back to the owning MediaRouterUI instance.
67 DISALLOW_COPY_AND_ASSIGN(UIIssuesObserver
);
70 MediaRouterUI::UIMediaRoutesObserver::UIMediaRoutesObserver(
72 const RoutesUpdatedCallback
& callback
)
73 : MediaRoutesObserver(router
), callback_(callback
) {
74 DCHECK(!callback_
.is_null());
77 MediaRouterUI::UIMediaRoutesObserver::~UIMediaRoutesObserver() {}
79 void MediaRouterUI::UIMediaRoutesObserver::OnRoutesUpdated(
80 const std::vector
<MediaRoute
>& routes
) {
81 std::vector
<MediaRoute
> routes_for_display
;
82 for (const MediaRoute
& route
: routes
) {
83 if (route
.for_display()) {
85 for (const MediaRoute
& existing_route
: routes_for_display
) {
86 if (existing_route
.media_sink_id() == route
.media_sink_id()) {
87 DVLOG(2) << "Received another route for display with the same sink"
88 << " id as an existing route. "
89 << route
.media_route_id() << " has the same sink id as "
90 << existing_route
.media_sink_id() << ".";
94 routes_for_display
.push_back(route
);
98 callback_
.Run(routes_for_display
);
101 MediaRouterUI::MediaRouterUI(content::WebUI
* web_ui
)
102 : ConstrainedWebDialogUI(web_ui
),
103 handler_(new MediaRouterWebUIMessageHandler(this)),
104 ui_initialized_(false),
105 has_pending_route_request_(false),
106 requesting_route_for_default_source_(false),
109 weak_factory_(this) {
110 // Create a WebUIDataSource containing the chrome://media-router page's
112 scoped_ptr
<content::WebUIDataSource
> html_source(
113 content::WebUIDataSource::Create(chrome::kChromeUIMediaRouterHost
));
115 content::WebContents
* wc
= web_ui
->GetWebContents();
118 router_
= static_cast<MediaRouterMojoImpl
*>(
119 MediaRouterFactory::GetApiForBrowserContext(wc
->GetBrowserContext()));
121 // Allows UI to load extensionview.
122 // TODO(haibinlu): limit object-src to current extension once crbug/514866
124 html_source
->OverrideContentSecurityPolicyObjectSrc("object-src *;");
126 AddLocalizedStrings(html_source
.get());
127 AddMediaRouterUIResources(html_source
.get());
128 // Ownership of |html_source| is transferred to the BrowserContext.
129 content::WebUIDataSource::Add(Profile::FromWebUI(web_ui
),
130 html_source
.release());
132 // Ownership of |handler_| is transferred to |web_ui|.
133 web_ui
->AddMessageHandler(handler_
);
136 MediaRouterUI::~MediaRouterUI() {
137 if (query_result_manager_
.get())
138 query_result_manager_
->RemoveObserver(this);
139 if (presentation_service_delegate_
.get())
140 presentation_service_delegate_
->RemoveDefaultMediaSourceObserver(this);
141 // If |presentation_request_| still exists, then it means presentation route
142 // request was never attempted.
143 if (presentation_request_
) {
144 presentation_request_
->InvokeErrorCallback(content::PresentationError(
145 content::PRESENTATION_ERROR_SESSION_REQUEST_CANCELLED
,
150 void MediaRouterUI::InitWithDefaultMediaSource(
151 const base::WeakPtr
<PresentationServiceDelegateImpl
>& delegate
) {
153 DCHECK(!presentation_service_delegate_
);
154 DCHECK(!query_result_manager_
.get());
156 presentation_service_delegate_
= delegate
;
157 presentation_service_delegate_
->AddDefaultMediaSourceObserver(this);
158 InitCommon(presentation_service_delegate_
->web_contents(),
159 presentation_service_delegate_
->default_source(),
160 presentation_service_delegate_
->default_frame_url());
163 void MediaRouterUI::InitWithPresentationSessionRequest(
164 content::WebContents
* initiator
,
165 const base::WeakPtr
<PresentationServiceDelegateImpl
>& delegate
,
166 scoped_ptr
<CreatePresentationSessionRequest
> presentation_request
) {
168 DCHECK(presentation_request
);
169 DCHECK(!presentation_request_
);
170 DCHECK(!query_result_manager_
);
172 presentation_request_
= presentation_request
.Pass();
173 presentation_service_delegate_
= delegate
;
174 InitCommon(initiator
, presentation_request_
->media_source(),
175 presentation_request_
->frame_url());
178 void MediaRouterUI::InitCommon(content::WebContents
* initiator
,
179 const MediaSource
& default_source
,
180 const GURL
& default_frame_url
) {
184 // Register for Issue and MediaRoute updates.
185 issues_observer_
.reset(new UIIssuesObserver(router_
, this));
186 routes_observer_
.reset(new UIMediaRoutesObserver(
188 base::Bind(&MediaRouterUI::OnRoutesUpdated
, base::Unretained(this))));
190 query_result_manager_
.reset(new QueryResultManager(router_
));
191 query_result_manager_
->AddObserver(this);
193 // These modes are always available.
194 query_result_manager_
->StartSinksQuery(
195 MediaCastMode::DESKTOP_MIRROR
, MediaSourceForDesktop());
196 initiator_
= initiator
;
197 MediaSource
mirroring_source(
198 MediaSourceForTab(SessionTabHelper::IdForTab(initiator
)));
199 query_result_manager_
->StartSinksQuery(
200 MediaCastMode::TAB_MIRROR
, mirroring_source
);
202 OnDefaultMediaSourceChanged(default_source
, default_frame_url
);
205 void MediaRouterUI::OnDefaultMediaSourceChanged(const MediaSource
& source
,
206 const GURL
& frame_url
) {
207 if (source
.Empty()) {
208 query_result_manager_
->StopSinksQuery(MediaCastMode::DEFAULT
);
210 query_result_manager_
->StartSinksQuery(MediaCastMode::DEFAULT
, source
);
212 UpdateSourceHostAndCastModes(frame_url
);
215 void MediaRouterUI::UpdateSourceHostAndCastModes(const GURL
& frame_url
) {
216 DCHECK(query_result_manager_
);
217 frame_url_
= frame_url
;
218 query_result_manager_
->GetSupportedCastModes(&cast_modes_
);
220 handler_
->UpdateCastModes(cast_modes_
, GetHostFromURL(frame_url_
));
223 void MediaRouterUI::Close() {
224 ConstrainedWebDialogDelegate
* delegate
= GetConstrainedDelegate();
226 delegate
->GetWebDialogDelegate()->OnDialogClosed(std::string());
227 delegate
->OnDialogCloseFromWebUI();
231 void MediaRouterUI::UIInitialized() {
232 ui_initialized_
= true;
235 bool MediaRouterUI::CreateRoute(const MediaSink::Id
& sink_id
) {
236 return DoCreateRoute(sink_id
, GetPreferredCastMode(cast_modes_
));
239 bool MediaRouterUI::CreateRouteWithCastModeOverride(
240 const MediaSink::Id
& sink_id
,
241 MediaCastMode cast_mode_override
) {
242 // NOTE: It's actually not an override if
243 // |cast_mode_override| == |GetPreferredCastMode(cast_modes_)|.
244 return DoCreateRoute(sink_id
, cast_mode_override
);
247 void MediaRouterUI::CloseRoute(const MediaRoute::Id
& route_id
) {
248 router_
->CloseRoute(route_id
);
251 void MediaRouterUI::AddIssue(const Issue
& issue
) {
252 router_
->AddIssue(issue
);
255 void MediaRouterUI::ClearIssue(const std::string
& issue_id
) {
256 router_
->ClearIssue(issue_id
);
259 std::string
MediaRouterUI::GetInitialHeaderText() const {
260 if (cast_modes_
.empty())
261 return std::string();
263 return MediaCastModeToDescription(GetPreferredCastMode(cast_modes_
),
264 GetHostFromURL(frame_url_
));
267 std::string
MediaRouterUI::GetInitialHeaderTextTooltip() const {
268 if (cast_modes_
.empty())
269 return std::string();
271 return GetHostFromURL(frame_url_
);
274 void MediaRouterUI::OnResultsUpdated(
275 const std::vector
<MediaSinkWithCastModes
>& sinks
) {
278 handler_
->UpdateSinks(sinks_
);
281 void MediaRouterUI::SetIssue(const Issue
* issue
) {
283 handler_
->UpdateIssue(issue
);
286 void MediaRouterUI::OnRoutesUpdated(const std::vector
<MediaRoute
>& routes
) {
289 handler_
->UpdateRoutes(routes_
);
292 void MediaRouterUI::OnRouteResponseReceived(const MediaSink::Id
& sink_id
,
293 const MediaRoute
* route
,
294 const std::string
& presentation_id
,
295 const std::string
& error
) {
296 DVLOG(1) << "OnRouteResponseReceived";
298 // The provider will handle sending an issue for a failed route request.
299 DVLOG(0) << "MediaRouteResponse returned error: " << error
;
302 handler_
->OnCreateRouteResponseReceived(sink_id
, route
);
303 has_pending_route_request_
= false;
304 requesting_route_for_default_source_
= false;
307 bool MediaRouterUI::DoCreateRoute(const MediaSink::Id
& sink_id
,
308 MediaCastMode cast_mode
) {
309 DCHECK(query_result_manager_
.get());
312 // Note that there is a rarely-encountered bug, where the MediaCastMode to
313 // MediaSource mapping could have been updated, between when the user
314 // clicked on the UI to start a create route request,
315 // and when this function is called.
316 // However, since the user does not have visibility into the MediaSource, and
317 // that it occurs very rarely in practice, we leave it as-is for now.
318 MediaSource source
= query_result_manager_
->GetSourceForCastMode(cast_mode
);
319 if (source
.Empty()) {
320 LOG(ERROR
) << "No corresponding MediaSource for cast mode " << cast_mode
;
324 has_pending_route_request_
= true;
325 requesting_route_for_default_source_
= cast_mode
== MediaCastMode::DEFAULT
;
327 // TODO(imcheng): What is the origin if not creating route in DEFAULT mode?
328 if (requesting_route_for_default_source_
) {
329 origin
= frame_url_
.GetOrigin();
331 // Requesting route for mirroring. Use a placeholder URL as origin.
332 origin
= GURL(chrome::kChromeUIMediaRouterURL
);
334 DCHECK(origin
.is_valid());
336 DVLOG(1) << "DoCreateRoute: origin: " << origin
;
338 // There are 3 cases. In all cases the MediaRouterUI will need to be notified.
339 // (1) Non-presentation route request (e.g., mirroring). No additional
340 // notification necessary.
341 // (2) Presentation route request for a Presentation API startSession call.
342 // The startSession (CreatePresentationSessionRequest) will need to be
345 // (3) Browser-initiated presentation route request. If successful,
346 // PresentationServiceDelegateImpl will have to be notified. Note that we
347 // treat subsequent route requests from a Presentation API-initiated dialogs
348 // as browser-initiated.
349 std::vector
<MediaRouteResponseCallback
> route_response_callbacks
;
350 route_response_callbacks
.push_back(
351 base::Bind(&MediaRouterUI::OnRouteResponseReceived
,
352 weak_factory_
.GetWeakPtr(), sink_id
));
353 if (requesting_route_for_default_source_
) {
354 if (presentation_request_
) {
355 // |presentation_request_| will be nullptr after this call, as the
356 // object will be transferred to the callback.
357 route_response_callbacks
.push_back(
358 base::Bind(&CreatePresentationSessionRequest::HandleRouteResponse
,
359 base::Passed(&presentation_request_
)));
360 } else if (presentation_service_delegate_
) {
361 route_response_callbacks
.push_back(
362 base::Bind(&PresentationServiceDelegateImpl::OnRouteResponse
,
363 presentation_service_delegate_
));
367 router_
->CreateRoute(source
.id(), sink_id
, origin
,
368 SessionTabHelper::IdForTab(initiator_
),
369 route_response_callbacks
);
373 std::string
MediaRouterUI::GetFrameURLHost() const {
374 return GetHostFromURL(frame_url_
);
377 const std::string
& MediaRouterUI::GetRouteProviderExtensionId() const {
378 return router_
->media_route_provider_extension_id();
381 } // namespace media_router