| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/media/router/presentation/presentation_service_delegate_impl.h" |
| |
| #include <map> |
| #include <string> |
| #include <unordered_map> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/containers/small_map.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/media/router/media_router.h" |
| #include "chrome/browser/media/router/media_router_dialog_controller.h" |
| #include "chrome/browser/media/router/media_router_factory.h" |
| #include "chrome/browser/media/router/media_router_metrics.h" |
| #include "chrome/browser/media/router/presentation/browser_presentation_connection_proxy.h" |
| #include "chrome/browser/media/router/presentation/local_presentation_manager.h" |
| #include "chrome/browser/media/router/presentation/local_presentation_manager_factory.h" |
| #include "chrome/browser/media/router/presentation/presentation_media_sinks_observer.h" |
| #include "chrome/browser/media/router/route_message_observer.h" |
| #include "chrome/common/media_router/media_route.h" |
| #include "chrome/common/media_router/media_sink.h" |
| #include "chrome/common/media_router/media_source_helper.h" |
| #include "chrome/common/media_router/route_request_result.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/presentation_request.h" |
| #include "content/public/browser/presentation_screen_availability_listener.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "url/gurl.h" |
| |
| #if !defined(OS_ANDROID) |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #endif |
| |
| using blink::mojom::PresentationConnectionPtr; |
| using blink::mojom::PresentationConnectionPtrInfo; |
| using blink::mojom::PresentationConnectionRequest; |
| using blink::mojom::PresentationError; |
| using blink::mojom::PresentationErrorType; |
| using blink::mojom::PresentationInfo; |
| using blink::mojom::ScreenAvailability; |
| using content::RenderFrameHost; |
| |
| namespace media_router { |
| |
| namespace { |
| |
| using DelegateObserver = content::PresentationServiceDelegate::Observer; |
| |
| // Gets the last committed URL for the render frame specified by |
| // |render_frame_host_id|. |
| url::Origin GetLastCommittedURLForFrame( |
| content::GlobalFrameRoutingId render_frame_host_id) { |
| RenderFrameHost* render_frame_host = RenderFrameHost::FromID( |
| render_frame_host_id.child_id, render_frame_host_id.frame_routing_id); |
| DCHECK(render_frame_host); |
| return render_frame_host->GetLastCommittedOrigin(); |
| } |
| |
| bool ArePresentationRequestsEqual( |
| const content::PresentationRequest& request1, |
| const content::PresentationRequest& request2) { |
| return request1.render_frame_host_id == request2.render_frame_host_id && |
| request1.presentation_urls == request2.presentation_urls && |
| ((request1.frame_origin.opaque() && request2.frame_origin.opaque()) || |
| (request1.frame_origin == request2.frame_origin)); |
| } |
| |
| } // namespace |
| |
| // PresentationFrame interfaces with MediaRouter to maintain the current state |
| // of Presentation API within a single render frame, such as the set of |
| // PresentationAvailability listeners and PresentationConnections. |
| // Instances are lazily created when certain Presentation API is invoked on a |
| // frame, and are owned by PresentationServiceDelegateImpl. |
| // Instances are destroyed when the corresponding frame navigates, or when it |
| // is destroyed. |
| class PresentationFrame { |
| public: |
| PresentationFrame(const content::GlobalFrameRoutingId& render_frame_host_id, |
| content::WebContents* web_contents, |
| MediaRouter* router); |
| ~PresentationFrame(); |
| |
| // Mirror corresponding APIs in PresentationServiceDelegateImpl. |
| bool SetScreenAvailabilityListener( |
| content::PresentationScreenAvailabilityListener* listener); |
| void RemoveScreenAvailabilityListener( |
| content::PresentationScreenAvailabilityListener* listener); |
| bool HasScreenAvailabilityListenerForTest( |
| const MediaSource::Id& source_id) const; |
| void ListenForConnectionStateChange( |
| const PresentationInfo& connection, |
| const content::PresentationConnectionStateChangedCallback& |
| state_changed_cb); |
| |
| void Reset(); |
| |
| MediaRoute::Id GetRouteId(const std::string& presentation_id) const; |
| |
| void AddPresentation(const PresentationInfo& presentation_info, |
| const MediaRoute& route); |
| void ConnectToPresentation( |
| const PresentationInfo& presentation_info, |
| PresentationConnectionPtr controller_connection_ptr, |
| PresentationConnectionRequest receiver_connection_request); |
| void RemovePresentation(const std::string& presentation_id); |
| |
| private: |
| base::small_map<std::map<std::string, MediaRoute>> presentation_id_to_route_; |
| base::small_map< |
| std::map<std::string, std::unique_ptr<PresentationMediaSinksObserver>>> |
| url_to_sinks_observer_; |
| std::unordered_map<MediaRoute::Id, |
| std::unique_ptr<PresentationConnectionStateSubscription>> |
| connection_state_subscriptions_; |
| std::unordered_map<MediaRoute::Id, |
| std::unique_ptr<BrowserPresentationConnectionProxy>> |
| browser_connection_proxies_; |
| |
| content::GlobalFrameRoutingId render_frame_host_id_; |
| |
| // References to the owning WebContents, and the corresponding MediaRouter. |
| content::WebContents* web_contents_; |
| MediaRouter* router_; |
| }; |
| |
| PresentationFrame::PresentationFrame( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| content::WebContents* web_contents, |
| MediaRouter* router) |
| : render_frame_host_id_(render_frame_host_id), |
| web_contents_(web_contents), |
| router_(router) { |
| DCHECK(web_contents_); |
| DCHECK(router_); |
| } |
| |
| PresentationFrame::~PresentationFrame() = default; |
| |
| MediaRoute::Id PresentationFrame::GetRouteId( |
| const std::string& presentation_id) const { |
| auto it = presentation_id_to_route_.find(presentation_id); |
| return it != presentation_id_to_route_.end() ? it->second.media_route_id() |
| : ""; |
| } |
| |
| bool PresentationFrame::SetScreenAvailabilityListener( |
| content::PresentationScreenAvailabilityListener* listener) { |
| GURL url = listener->GetAvailabilityUrl(); |
| if (!IsValidPresentationUrl(url)) { |
| listener->OnScreenAvailabilityChanged( |
| ScreenAvailability::SOURCE_NOT_SUPPORTED); |
| return false; |
| } |
| |
| MediaRouterMetrics::RecordPresentationUrlType(url); |
| |
| MediaSource source = MediaSourceForPresentationUrl(url); |
| auto& sinks_observer = url_to_sinks_observer_[source.id()]; |
| if (sinks_observer && sinks_observer->listener() == listener) |
| return false; |
| |
| sinks_observer.reset(new PresentationMediaSinksObserver( |
| router_, listener, source, |
| GetLastCommittedURLForFrame(render_frame_host_id_))); |
| |
| if (!sinks_observer->Init()) { |
| url_to_sinks_observer_.erase(source.id()); |
| listener->OnScreenAvailabilityChanged(ScreenAvailability::DISABLED); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void PresentationFrame::RemoveScreenAvailabilityListener( |
| content::PresentationScreenAvailabilityListener* listener) { |
| MediaSource source = |
| MediaSourceForPresentationUrl(listener->GetAvailabilityUrl()); |
| auto sinks_observer_it = url_to_sinks_observer_.find(source.id()); |
| if (sinks_observer_it != url_to_sinks_observer_.end() && |
| sinks_observer_it->second->listener() == listener) { |
| url_to_sinks_observer_.erase(sinks_observer_it); |
| } |
| } |
| |
| bool PresentationFrame::HasScreenAvailabilityListenerForTest( |
| const MediaSource::Id& source_id) const { |
| return url_to_sinks_observer_.find(source_id) != url_to_sinks_observer_.end(); |
| } |
| |
| void PresentationFrame::Reset() { |
| for (const auto& pid_route : presentation_id_to_route_) { |
| if (pid_route.second.is_local_presentation()) { |
| auto* local_presentation_manager = |
| LocalPresentationManagerFactory::GetOrCreateForWebContents( |
| web_contents_); |
| local_presentation_manager->UnregisterLocalPresentationController( |
| pid_route.first, render_frame_host_id_); |
| } else { |
| router_->DetachRoute(pid_route.second.media_route_id()); |
| } |
| } |
| |
| presentation_id_to_route_.clear(); |
| url_to_sinks_observer_.clear(); |
| connection_state_subscriptions_.clear(); |
| browser_connection_proxies_.clear(); |
| } |
| |
| void PresentationFrame::AddPresentation( |
| const PresentationInfo& presentation_info, |
| const MediaRoute& route) { |
| presentation_id_to_route_.emplace(presentation_info.id, route); |
| } |
| |
| void PresentationFrame::ConnectToPresentation( |
| const PresentationInfo& presentation_info, |
| PresentationConnectionPtr controller_connection_ptr, |
| PresentationConnectionRequest receiver_connection_request) { |
| const auto pid_route_it = |
| presentation_id_to_route_.find(presentation_info.id); |
| |
| if (pid_route_it == presentation_id_to_route_.end()) { |
| DLOG(WARNING) << "No route for [presentation_id]: " << presentation_info.id; |
| return; |
| } |
| |
| if (pid_route_it->second.is_local_presentation()) { |
| auto* local_presentation_manager = |
| LocalPresentationManagerFactory::GetOrCreateForWebContents( |
| web_contents_); |
| local_presentation_manager->RegisterLocalPresentationController( |
| presentation_info, render_frame_host_id_, |
| std::move(controller_connection_ptr), |
| std::move(receiver_connection_request), pid_route_it->second); |
| } else { |
| DVLOG(2) |
| << "Creating BrowserPresentationConnectionProxy for [presentation_id]: " |
| << presentation_info.id; |
| MediaRoute::Id route_id = pid_route_it->second.media_route_id(); |
| if (base::ContainsKey(browser_connection_proxies_, route_id)) { |
| DLOG(ERROR) << __func__ |
| << "Already has a BrowserPresentationConnectionProxy for " |
| << "route: " << route_id; |
| return; |
| } |
| |
| auto* proxy = new BrowserPresentationConnectionProxy( |
| router_, route_id, std::move(receiver_connection_request), |
| std::move(controller_connection_ptr)); |
| browser_connection_proxies_.emplace(route_id, base::WrapUnique(proxy)); |
| } |
| } |
| |
| void PresentationFrame::RemovePresentation(const std::string& presentation_id) { |
| // Remove the presentation id mapping so a later call to Reset is a no-op. |
| auto it = presentation_id_to_route_.find(presentation_id); |
| if (it == presentation_id_to_route_.end()) |
| return; |
| |
| auto route_id = it->second.media_route_id(); |
| presentation_id_to_route_.erase(presentation_id); |
| browser_connection_proxies_.erase(route_id); |
| // We keep the PresentationConnectionStateChangedCallback registered with MR |
| // so the MRP can tell us when terminate() completed. |
| } |
| |
| void PresentationFrame::ListenForConnectionStateChange( |
| const PresentationInfo& connection, |
| const content::PresentationConnectionStateChangedCallback& |
| state_changed_cb) { |
| auto it = presentation_id_to_route_.find(connection.id); |
| if (it == presentation_id_to_route_.end()) { |
| DLOG(ERROR) << __func__ |
| << "route id not found for presentation: " << connection.id; |
| return; |
| } |
| |
| const MediaRoute::Id& route_id = it->second.media_route_id(); |
| if (connection_state_subscriptions_.find(route_id) != |
| connection_state_subscriptions_.end()) { |
| DLOG(ERROR) << __func__ |
| << "Already listening connection state change for route: " |
| << route_id; |
| return; |
| } |
| |
| connection_state_subscriptions_.emplace( |
| route_id, router_->AddPresentationConnectionStateChangedCallback( |
| route_id, state_changed_cb)); |
| } |
| |
| StartPresentationContext::StartPresentationContext( |
| const content::PresentationRequest& presentation_request, |
| PresentationConnectionCallback success_cb, |
| PresentationConnectionErrorCallback error_cb) |
| : presentation_request_(presentation_request), |
| success_cb_(std::move(success_cb)), |
| error_cb_(std::move(error_cb)) { |
| DCHECK(success_cb_); |
| DCHECK(error_cb_); |
| } |
| |
| StartPresentationContext::~StartPresentationContext() { |
| if (success_cb_ && error_cb_) { |
| std::move(error_cb_).Run(blink::mojom::PresentationError( |
| blink::mojom::PresentationErrorType::UNKNOWN, "Unknown error.")); |
| } |
| } |
| |
| void StartPresentationContext::InvokeSuccessCallback( |
| const std::string& presentation_id, |
| const GURL& presentation_url, |
| const MediaRoute& route, |
| mojom::RoutePresentationConnectionPtr connection) { |
| if (success_cb_ && error_cb_) { |
| std::move(success_cb_) |
| .Run(blink::mojom::PresentationInfo(presentation_url, presentation_id), |
| std::move(connection), route); |
| } |
| } |
| |
| void StartPresentationContext::InvokeErrorCallback( |
| const blink::mojom::PresentationError& error) { |
| if (success_cb_ && error_cb_) { |
| std::move(error_cb_).Run(error); |
| } |
| } |
| |
| void StartPresentationContext::HandleRouteResponse( |
| mojom::RoutePresentationConnectionPtr connection, |
| const RouteRequestResult& result) { |
| if (!result.route()) { |
| InvokeErrorCallback(blink::mojom::PresentationError( |
| blink::mojom::PresentationErrorType::UNKNOWN, result.error())); |
| } else { |
| InvokeSuccessCallback(result.presentation_id(), result.presentation_url(), |
| *result.route(), std::move(connection)); |
| } |
| } |
| |
| PresentationServiceDelegateImpl* |
| PresentationServiceDelegateImpl::GetOrCreateForWebContents( |
| content::WebContents* web_contents) { |
| DCHECK(web_contents); |
| // CreateForWebContents does nothing if the delegate instance already exists. |
| PresentationServiceDelegateImpl::CreateForWebContents(web_contents); |
| return PresentationServiceDelegateImpl::FromWebContents(web_contents); |
| } |
| |
| PresentationServiceDelegateImpl::PresentationServiceDelegateImpl( |
| content::WebContents* web_contents) |
| : web_contents_(web_contents), |
| router_(MediaRouterFactory::GetApiForBrowserContext( |
| web_contents_->GetBrowserContext())), |
| weak_factory_(this) { |
| DCHECK(web_contents_); |
| DCHECK(router_); |
| } |
| |
| PresentationServiceDelegateImpl::~PresentationServiceDelegateImpl() = default; |
| |
| void PresentationServiceDelegateImpl::AddObserver(int render_process_id, |
| int render_frame_id, |
| DelegateObserver* observer) { |
| DCHECK(observer); |
| observers_.AddObserver(render_process_id, render_frame_id, observer); |
| } |
| |
| void PresentationServiceDelegateImpl::RemoveObserver(int render_process_id, |
| int render_frame_id) { |
| observers_.RemoveObserver(render_process_id, render_frame_id); |
| } |
| |
| bool PresentationServiceDelegateImpl::AddScreenAvailabilityListener( |
| int render_process_id, |
| int render_frame_id, |
| content::PresentationScreenAvailabilityListener* listener) { |
| DCHECK(listener); |
| content::GlobalFrameRoutingId render_frame_host_id(render_process_id, |
| render_frame_id); |
| auto* presentation_frame = GetOrAddPresentationFrame(render_frame_host_id); |
| return presentation_frame->SetScreenAvailabilityListener(listener); |
| } |
| |
| void PresentationServiceDelegateImpl::RemoveScreenAvailabilityListener( |
| int render_process_id, |
| int render_frame_id, |
| content::PresentationScreenAvailabilityListener* listener) { |
| DCHECK(listener); |
| content::GlobalFrameRoutingId render_frame_host_id(render_process_id, |
| render_frame_id); |
| const auto it = presentation_frames_.find(render_frame_host_id); |
| if (it != presentation_frames_.end()) |
| it->second->RemoveScreenAvailabilityListener(listener); |
| } |
| |
| void PresentationServiceDelegateImpl::Reset(int render_process_id, |
| int render_frame_id) { |
| content::GlobalFrameRoutingId render_frame_host_id(render_process_id, |
| render_frame_id); |
| const auto it = presentation_frames_.find(render_frame_host_id); |
| if (it != presentation_frames_.end()) { |
| it->second->Reset(); |
| presentation_frames_.erase(it); |
| } |
| |
| if (default_presentation_request_ && |
| render_frame_host_id == |
| default_presentation_request_->render_frame_host_id) { |
| ClearDefaultPresentationRequest(); |
| } |
| } |
| |
| PresentationFrame* PresentationServiceDelegateImpl::GetOrAddPresentationFrame( |
| const content::GlobalFrameRoutingId& render_frame_host_id) { |
| auto& presentation_frame = presentation_frames_[render_frame_host_id]; |
| if (!presentation_frame) { |
| presentation_frame.reset( |
| new PresentationFrame(render_frame_host_id, web_contents_, router_)); |
| } |
| return presentation_frame.get(); |
| } |
| |
| void PresentationServiceDelegateImpl::SetDefaultPresentationUrls( |
| const content::PresentationRequest& request, |
| content::DefaultPresentationConnectionCallback callback) { |
| if (request.presentation_urls.empty()) { |
| ClearDefaultPresentationRequest(); |
| return; |
| } |
| |
| DCHECK(!callback.is_null()); |
| default_presentation_started_callback_ = std::move(callback); |
| default_presentation_request_ = request; |
| for (auto& observer : default_presentation_request_observers_) |
| observer.OnDefaultPresentationChanged(*default_presentation_request_); |
| } |
| |
| void PresentationServiceDelegateImpl::OnJoinRouteResponse( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| const GURL& presentation_url, |
| const std::string& presentation_id, |
| content::PresentationConnectionCallback success_cb, |
| content::PresentationConnectionErrorCallback error_cb, |
| mojom::RoutePresentationConnectionPtr connection, |
| const RouteRequestResult& result) { |
| if (!result.route()) { |
| std::move(error_cb).Run(PresentationError( |
| PresentationErrorType::NO_PRESENTATION_FOUND, result.error())); |
| } else { |
| DVLOG(1) << "OnJoinRouteResponse: " |
| << "route_id: " << result.route()->media_route_id() |
| << ", presentation URL: " << presentation_url |
| << ", presentation ID: " << presentation_id; |
| DCHECK_EQ(presentation_id, result.presentation_id()); |
| PresentationInfo presentation_info(presentation_url, |
| result.presentation_id()); |
| AddPresentation(render_frame_host_id, presentation_info, *result.route()); |
| EnsurePresentationConnection(render_frame_host_id, presentation_info, |
| &connection); |
| std::move(success_cb) |
| .Run(blink::mojom::PresentationConnectionResult::New( |
| presentation_info.Clone(), std::move(connection->connection_ptr), |
| std::move(connection->connection_request))); |
| } |
| } |
| |
| void PresentationServiceDelegateImpl::OnStartPresentationSucceeded( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| content::PresentationConnectionCallback success_cb, |
| const PresentationInfo& new_presentation_info, |
| mojom::RoutePresentationConnectionPtr connection, |
| const MediaRoute& route) { |
| DVLOG(1) << "OnStartPresentationSucceeded: " |
| << "route_id: " << route.media_route_id() |
| << ", presentation URL: " << new_presentation_info.url |
| << ", presentation ID: " << new_presentation_info.id; |
| AddPresentation(render_frame_host_id, new_presentation_info, route); |
| EnsurePresentationConnection(render_frame_host_id, new_presentation_info, |
| &connection); |
| std::move(success_cb) |
| .Run(blink::mojom::PresentationConnectionResult::New( |
| new_presentation_info.Clone(), std::move(connection->connection_ptr), |
| std::move(connection->connection_request))); |
| } |
| |
| void PresentationServiceDelegateImpl::AddPresentation( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| const PresentationInfo& presentation_info, |
| const MediaRoute& route) { |
| auto* presentation_frame = GetOrAddPresentationFrame(render_frame_host_id); |
| presentation_frame->AddPresentation(presentation_info, route); |
| } |
| |
| void PresentationServiceDelegateImpl::RemovePresentation( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| const std::string& presentation_id) { |
| const auto it = presentation_frames_.find(render_frame_host_id); |
| if (it != presentation_frames_.end()) |
| it->second->RemovePresentation(presentation_id); |
| } |
| |
| void PresentationServiceDelegateImpl::StartPresentation( |
| const content::PresentationRequest& request, |
| content::PresentationConnectionCallback success_cb, |
| content::PresentationConnectionErrorCallback error_cb) { |
| const auto& render_frame_host_id = request.render_frame_host_id; |
| const auto& presentation_urls = request.presentation_urls; |
| if (presentation_urls.empty()) { |
| std::move(error_cb).Run(PresentationError( |
| PresentationErrorType::UNKNOWN, "Invalid presentation arguments.")); |
| return; |
| } |
| if (std::find_if_not(presentation_urls.begin(), presentation_urls.end(), |
| IsValidPresentationUrl) != presentation_urls.end()) { |
| std::move(error_cb).Run( |
| PresentationError(PresentationErrorType::NO_PRESENTATION_FOUND, |
| "Invalid presentation URL.")); |
| return; |
| } |
| auto presentation_context = std::make_unique<StartPresentationContext>( |
| request, |
| base::BindOnce( |
| &PresentationServiceDelegateImpl::OnStartPresentationSucceeded, |
| GetWeakPtr(), render_frame_host_id, std::move(success_cb)), |
| std::move(error_cb)); |
| if (start_presentation_cb_) { |
| start_presentation_cb_.Run(std::move(presentation_context)); |
| return; |
| } |
| MediaRouterDialogController* controller = |
| MediaRouterDialogController::GetOrCreateForWebContents(web_contents_); |
| if (!controller->ShowMediaRouterDialogForPresentation( |
| std::move(presentation_context))) { |
| LOG(ERROR) |
| << "StartPresentation failed: unable to create Media Router dialog."; |
| } |
| } |
| |
| void PresentationServiceDelegateImpl::ReconnectPresentation( |
| const content::PresentationRequest& request, |
| const std::string& presentation_id, |
| content::PresentationConnectionCallback success_cb, |
| content::PresentationConnectionErrorCallback error_cb) { |
| DVLOG(2) << "PresentationServiceDelegateImpl::ReconnectPresentation"; |
| const auto& presentation_urls = request.presentation_urls; |
| const auto& render_frame_host_id = request.render_frame_host_id; |
| if (presentation_urls.empty()) { |
| std::move(error_cb).Run( |
| PresentationError(PresentationErrorType::NO_PRESENTATION_FOUND, |
| "Invalid presentation arguments.")); |
| return; |
| } |
| |
| #if !defined(OS_ANDROID) |
| if (IsAutoJoinPresentationId(presentation_id) && |
| ShouldCancelAutoJoinForOrigin(request.frame_origin)) { |
| std::move(error_cb).Run( |
| PresentationError(PresentationErrorType::PRESENTATION_REQUEST_CANCELLED, |
| "Auto-join request cancelled by user preferences.")); |
| return; |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| auto* local_presentation_manager = |
| LocalPresentationManagerFactory::GetOrCreateForWebContents(web_contents_); |
| // Check local presentation across frames. |
| if (local_presentation_manager->IsLocalPresentation(presentation_id)) { |
| auto* route = local_presentation_manager->GetRoute(presentation_id); |
| |
| if (!route) { |
| LOG(WARNING) << "No route found for [presentation_id]: " |
| << presentation_id; |
| return; |
| } |
| |
| if (!base::ContainsValue(presentation_urls, route->media_source().url())) { |
| DVLOG(2) << "Presentation URLs do not match URL of current presentation:" |
| << route->media_source().url(); |
| return; |
| } |
| |
| auto result = RouteRequestResult::FromSuccess(*route, presentation_id); |
| OnJoinRouteResponse(render_frame_host_id, presentation_urls[0], |
| presentation_id, std::move(success_cb), |
| std::move(error_cb), |
| mojom::RoutePresentationConnectionPtr(), *result); |
| } else { |
| // TODO(crbug.com/627655): Handle multiple URLs. |
| const GURL& presentation_url = presentation_urls[0]; |
| bool incognito = web_contents_->GetBrowserContext()->IsOffTheRecord(); |
| router_->JoinRoute( |
| MediaSourceForPresentationUrl(presentation_url).id(), presentation_id, |
| request.frame_origin, web_contents_, |
| base::BindOnce(&PresentationServiceDelegateImpl::OnJoinRouteResponse, |
| GetWeakPtr(), render_frame_host_id, presentation_url, |
| presentation_id, std::move(success_cb), |
| std::move(error_cb)), |
| base::TimeDelta(), incognito); |
| } |
| } |
| |
| void PresentationServiceDelegateImpl::CloseConnection( |
| int render_process_id, |
| int render_frame_id, |
| const std::string& presentation_id) { |
| const content::GlobalFrameRoutingId rfh_id(render_process_id, |
| render_frame_id); |
| auto route_id = GetRouteId(rfh_id, presentation_id); |
| if (route_id.empty()) { |
| DVLOG(1) << "No active route for: " << presentation_id; |
| return; |
| } |
| |
| auto* local_presentation_manager = |
| LocalPresentationManagerFactory::GetOrCreateForWebContents(web_contents_); |
| |
| if (local_presentation_manager->IsLocalPresentation(presentation_id)) { |
| local_presentation_manager->UnregisterLocalPresentationController( |
| presentation_id, rfh_id); |
| } else { |
| router_->DetachRoute(route_id); |
| } |
| RemovePresentation(rfh_id, presentation_id); |
| // TODO(mfoltz): close() should always succeed so there is no need to keep the |
| // state_changed_cb around - remove it and fire the ChangeEvent on the |
| // PresentationConnection in Blink. |
| } |
| |
| void PresentationServiceDelegateImpl::Terminate( |
| int render_process_id, |
| int render_frame_id, |
| const std::string& presentation_id) { |
| const content::GlobalFrameRoutingId rfh_id(render_process_id, |
| render_frame_id); |
| auto route_id = GetRouteId(rfh_id, presentation_id); |
| if (route_id.empty()) { |
| DVLOG(1) << "No active route for: " << presentation_id; |
| return; |
| } |
| router_->TerminateRoute(route_id); |
| RemovePresentation(rfh_id, presentation_id); |
| } |
| |
| void PresentationServiceDelegateImpl::ListenForConnectionStateChange( |
| int render_process_id, |
| int render_frame_id, |
| const PresentationInfo& connection, |
| const content::PresentationConnectionStateChangedCallback& |
| state_changed_cb) { |
| content::GlobalFrameRoutingId render_frame_host_id(render_process_id, |
| render_frame_id); |
| const auto it = presentation_frames_.find(render_frame_host_id); |
| if (it != presentation_frames_.end()) |
| it->second->ListenForConnectionStateChange(connection, state_changed_cb); |
| } |
| |
| void PresentationServiceDelegateImpl::OnRouteResponse( |
| const content::PresentationRequest& presentation_request, |
| mojom::RoutePresentationConnectionPtr connection, |
| const RouteRequestResult& result) { |
| if (!result.route() || |
| !base::ContainsValue(presentation_request.presentation_urls, |
| result.presentation_url())) { |
| return; |
| } |
| |
| PresentationInfo presentation_info(result.presentation_url(), |
| result.presentation_id()); |
| AddPresentation(presentation_request.render_frame_host_id, presentation_info, |
| *result.route()); |
| if (default_presentation_request_ && |
| ArePresentationRequestsEqual(*default_presentation_request_, |
| presentation_request)) { |
| EnsurePresentationConnection(presentation_request.render_frame_host_id, |
| presentation_info, &connection); |
| default_presentation_started_callback_.Run( |
| blink::mojom::PresentationConnectionResult::New( |
| presentation_info.Clone(), std::move(connection->connection_ptr), |
| std::move(connection->connection_request))); |
| } else { |
| DCHECK(!connection); |
| } |
| } |
| |
| void PresentationServiceDelegateImpl::AddDefaultPresentationRequestObserver( |
| DefaultPresentationRequestObserver* observer) { |
| default_presentation_request_observers_.AddObserver(observer); |
| } |
| |
| void PresentationServiceDelegateImpl::RemoveDefaultPresentationRequestObserver( |
| DefaultPresentationRequestObserver* observer) { |
| default_presentation_request_observers_.RemoveObserver(observer); |
| } |
| |
| const content::PresentationRequest& |
| PresentationServiceDelegateImpl::GetDefaultPresentationRequest() const { |
| DCHECK(HasDefaultPresentationRequest()); |
| return *default_presentation_request_; |
| } |
| |
| bool PresentationServiceDelegateImpl::HasDefaultPresentationRequest() const { |
| return !!default_presentation_request_; |
| } |
| |
| base::WeakPtr<PresentationServiceDelegateImpl> |
| PresentationServiceDelegateImpl::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| bool PresentationServiceDelegateImpl::HasScreenAvailabilityListenerForTest( |
| int render_process_id, |
| int render_frame_id, |
| const MediaSource::Id& source_id) const { |
| content::GlobalFrameRoutingId render_frame_host_id(render_process_id, |
| render_frame_id); |
| const auto it = presentation_frames_.find(render_frame_host_id); |
| return it != presentation_frames_.end() && |
| it->second->HasScreenAvailabilityListenerForTest(source_id); |
| } |
| |
| void PresentationServiceDelegateImpl::ClearDefaultPresentationRequest() { |
| default_presentation_started_callback_.Reset(); |
| if (!default_presentation_request_) |
| return; |
| |
| default_presentation_request_.reset(); |
| for (auto& observer : default_presentation_request_observers_) |
| observer.OnDefaultPresentationRemoved(); |
| } |
| |
| std::unique_ptr<media::FlingingController> |
| PresentationServiceDelegateImpl::GetFlingingController( |
| int render_process_id, |
| int render_frame_id, |
| const std::string& presentation_id) { |
| const content::GlobalFrameRoutingId rfh_id(render_process_id, |
| render_frame_id); |
| MediaRoute::Id route_id = GetRouteId(rfh_id, presentation_id); |
| |
| if (route_id.empty()) |
| return nullptr; |
| |
| return router_->GetFlingingController(route_id); |
| } |
| |
| MediaRoute::Id PresentationServiceDelegateImpl::GetRouteId( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| const std::string& presentation_id) const { |
| const auto it = presentation_frames_.find(render_frame_host_id); |
| return it != presentation_frames_.end() |
| ? it->second->GetRouteId(presentation_id) |
| : MediaRoute::Id(); |
| } |
| |
| #if !defined(OS_ANDROID) |
| bool PresentationServiceDelegateImpl::ShouldCancelAutoJoinForOrigin( |
| const url::Origin& origin) const { |
| const base::ListValue* origins = |
| Profile::FromBrowserContext(web_contents_->GetBrowserContext()) |
| ->GetPrefs() |
| ->GetList(prefs::kMediaRouterTabMirroringSources); |
| return origins && |
| origins->Find(base::Value(origin.Serialize())) != origins->end(); |
| } |
| #endif // !defined(OS_ANDROID) |
| |
| void PresentationServiceDelegateImpl::EnsurePresentationConnection( |
| const content::GlobalFrameRoutingId& render_frame_host_id, |
| const PresentationInfo& presentation_info, |
| mojom::RoutePresentationConnectionPtr* connection) { |
| // This is where we ensure we have a valid Mojo pipe pair when starting or |
| // joining a presentation. If the MRP chose not return a pair of pipes |
| // directly, we need to provide a BrowserPresentationConnectionProxy here or |
| // connect to the LocalPresentationManager, as necessary. |
| if (!*connection) { |
| PresentationConnectionPtr controller_ptr; |
| PresentationConnectionPtrInfo receiver_ptr_info; |
| *connection = mojom::RoutePresentationConnection::New( |
| std::move(receiver_ptr_info), mojo::MakeRequest(&controller_ptr)); |
| auto* presentation_frame = GetOrAddPresentationFrame(render_frame_host_id); |
| presentation_frame->ConnectToPresentation( |
| presentation_info, std::move(controller_ptr), |
| mojo::MakeRequest(&(*connection)->connection_ptr)); |
| } |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(PresentationServiceDelegateImpl) |
| |
| } // namespace media_router |