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 "content/browser/presentation/presentation_service_impl.h"
9 #include "base/logging.h"
10 #include "content/browser/presentation/presentation_type_converters.h"
11 #include "content/public/browser/content_browser_client.h"
12 #include "content/public/browser/navigation_details.h"
13 #include "content/public/browser/render_frame_host.h"
14 #include "content/public/browser/render_process_host.h"
15 #include "content/public/browser/web_contents.h"
16 #include "content/public/common/content_client.h"
17 #include "content/public/common/frame_navigate_params.h"
21 PresentationServiceImpl::PresentationServiceImpl(
22 RenderFrameHost
* render_frame_host
,
23 WebContents
* web_contents
,
24 PresentationServiceDelegate
* delegate
)
25 : WebContentsObserver(web_contents
),
26 render_frame_host_(render_frame_host
),
28 is_start_session_pending_(false),
29 next_request_session_id_(0),
31 DCHECK(render_frame_host_
);
33 DVLOG(2) << "PresentationServiceImpl: "
34 << render_frame_host_
->GetProcess()->GetID() << ", "
35 << render_frame_host_
->GetRoutingID();
37 delegate_
->AddObserver(this);
40 PresentationServiceImpl::~PresentationServiceImpl() {
42 delegate_
->RemoveObserver(this);
43 FlushNewSessionCallbacks();
47 void PresentationServiceImpl::CreateMojoService(
48 RenderFrameHost
* render_frame_host
,
49 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
50 DVLOG(2) << "CreateMojoService";
51 WebContents
* web_contents
=
52 WebContents::FromRenderFrameHost(render_frame_host
);
55 // This object will be deleted when the RenderFrameHost is about to be
56 // deleted (RenderFrameDeleted) or if a connection error occurred
57 // (OnConnectionError).
58 PresentationServiceImpl
* impl
= new PresentationServiceImpl(
61 GetContentClient()->browser()->GetPresentationServiceDelegate(
63 impl
->Bind(request
.Pass());
66 void PresentationServiceImpl::Bind(
67 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
68 binding_
.reset(new mojo::Binding
<presentation::PresentationService
>(
69 this, request
.Pass()));
70 binding_
->set_error_handler(this);
73 void PresentationServiceImpl::OnConnectionError() {
74 DVLOG(1) << "OnConnectionError";
78 PresentationServiceImpl::ScreenAvailabilityContext
*
79 PresentationServiceImpl::GetOrCreateAvailabilityContext(
80 const std::string
& presentation_url
) {
81 auto it
= availability_contexts_
.find(presentation_url
);
82 if (it
== availability_contexts_
.end()) {
83 linked_ptr
<ScreenAvailabilityContext
> context(
84 new ScreenAvailabilityContext(presentation_url
));
85 if (!delegate_
->AddScreenAvailabilityListener(
86 render_frame_host_
->GetProcess()->GetID(),
87 render_frame_host_
->GetRoutingID(),
89 DVLOG(1) << "AddScreenAvailabilityListener failed. Ignoring request.";
92 it
= availability_contexts_
.insert(
93 std::make_pair(context
->GetPresentationUrl(), context
)).first
;
95 return it
->second
.get();
98 void PresentationServiceImpl::ListenForScreenAvailability(
99 const mojo::String
& presentation_url
,
100 const ScreenAvailabilityMojoCallback
& callback
) {
101 DVLOG(2) << "ListenForScreenAvailability";
103 callback
.Run(presentation_url
, false);
107 ScreenAvailabilityContext
* context
=
108 GetOrCreateAvailabilityContext(presentation_url
.get());
110 callback
.Run(presentation_url
, false);
113 context
->CallbackReceived(callback
);
116 void PresentationServiceImpl::RemoveScreenAvailabilityListener(
117 const mojo::String
& presentation_url
) {
118 DVLOG(2) << "RemoveScreenAvailabilityListener";
122 const std::string
& presentation_url_str
= presentation_url
.get();
123 auto it
= availability_contexts_
.find(presentation_url_str
);
124 if (it
== availability_contexts_
.end())
127 delegate_
->RemoveScreenAvailabilityListener(
128 render_frame_host_
->GetProcess()->GetID(),
129 render_frame_host_
->GetRoutingID(),
131 // Resolve the context's pending callbacks before removing it.
132 it
->second
->OnScreenAvailabilityChanged(false);
133 availability_contexts_
.erase(it
);
136 void PresentationServiceImpl::ListenForDefaultSessionStart(
137 const DefaultSessionMojoCallback
& callback
) {
141 void PresentationServiceImpl::StartSession(
142 const mojo::String
& presentation_url
,
143 const mojo::String
& presentation_id
,
144 const NewSessionMojoCallback
& callback
) {
145 DVLOG(2) << "StartSession";
147 InvokeNewSessionMojoCallbackWithError(callback
);
151 if (is_start_session_pending_
) {
152 queued_start_session_requests_
.push_back(make_linked_ptr(
153 new StartSessionRequest(presentation_url
, presentation_id
, callback
)));
157 DoStartSession(presentation_url
, presentation_id
, callback
);
160 void PresentationServiceImpl::JoinSession(
161 const mojo::String
& presentation_url
,
162 const mojo::String
& presentation_id
,
163 const NewSessionMojoCallback
& callback
) {
164 DVLOG(2) << "JoinSession";
166 InvokeNewSessionMojoCallbackWithError(callback
);
170 int request_session_id
= RegisterNewSessionCallback(callback
);
171 delegate_
->JoinSession(
172 render_frame_host_
->GetProcess()->GetID(),
173 render_frame_host_
->GetRoutingID(),
176 base::Bind(&PresentationServiceImpl::OnStartOrJoinSessionSucceeded
,
177 weak_factory_
.GetWeakPtr(), false, request_session_id
),
178 base::Bind(&PresentationServiceImpl::OnStartOrJoinSessionError
,
179 weak_factory_
.GetWeakPtr(), false, request_session_id
));
182 void PresentationServiceImpl::HandleQueuedStartSessionRequests() {
183 if (queued_start_session_requests_
.empty()) {
184 is_start_session_pending_
= false;
187 linked_ptr
<StartSessionRequest
> request
=
188 queued_start_session_requests_
.front();
189 queued_start_session_requests_
.pop_front();
190 DoStartSession(request
->presentation_url(),
191 request
->presentation_id(),
192 request
->PassCallback());
195 int PresentationServiceImpl::RegisterNewSessionCallback(
196 const NewSessionMojoCallback
& callback
) {
197 ++next_request_session_id_
;
198 pending_session_cbs_
[next_request_session_id_
].reset(
199 new NewSessionMojoCallback(callback
));
200 return next_request_session_id_
;
203 void PresentationServiceImpl::FlushNewSessionCallbacks() {
204 for (auto& pending_entry
: pending_session_cbs_
) {
205 InvokeNewSessionMojoCallbackWithError(*pending_entry
.second
);
207 pending_session_cbs_
.clear();
210 void PresentationServiceImpl::DoStartSession(
211 const std::string
& presentation_url
,
212 const std::string
& presentation_id
,
213 const NewSessionMojoCallback
& callback
) {
214 int request_session_id
= RegisterNewSessionCallback(callback
);
215 is_start_session_pending_
= true;
216 delegate_
->StartSession(
217 render_frame_host_
->GetProcess()->GetID(),
218 render_frame_host_
->GetRoutingID(),
221 base::Bind(&PresentationServiceImpl::OnStartOrJoinSessionSucceeded
,
222 weak_factory_
.GetWeakPtr(), true, request_session_id
),
223 base::Bind(&PresentationServiceImpl::OnStartOrJoinSessionError
,
224 weak_factory_
.GetWeakPtr(), true, request_session_id
));
227 void PresentationServiceImpl::OnStartOrJoinSessionSucceeded(
228 bool is_start_session
,
229 int request_session_id
,
230 const PresentationSessionInfo
& session_info
) {
231 RunAndEraseNewSessionMojoCallback(
233 presentation::PresentationSessionInfo::From(session_info
),
234 presentation::PresentationErrorPtr());
235 if (is_start_session
)
236 HandleQueuedStartSessionRequests();
239 void PresentationServiceImpl::OnStartOrJoinSessionError(
240 bool is_start_session
,
241 int request_session_id
,
242 const PresentationError
& error
) {
243 RunAndEraseNewSessionMojoCallback(
245 presentation::PresentationSessionInfoPtr(),
246 presentation::PresentationError::From(error
));
247 if (is_start_session
)
248 HandleQueuedStartSessionRequests();
251 void PresentationServiceImpl::RunAndEraseNewSessionMojoCallback(
252 int request_session_id
,
253 presentation::PresentationSessionInfoPtr session
,
254 presentation::PresentationErrorPtr error
) {
255 auto it
= pending_session_cbs_
.find(request_session_id
);
256 if (it
== pending_session_cbs_
.end())
259 DCHECK(it
->second
.get());
260 it
->second
->Run(session
.Pass(), error
.Pass());
261 pending_session_cbs_
.erase(it
);
264 void PresentationServiceImpl::DoSetDefaultPresentationUrl(
265 const std::string
& default_presentation_url
,
266 const std::string
& default_presentation_id
) {
268 delegate_
->SetDefaultPresentationUrl(
269 render_frame_host_
->GetProcess()->GetID(),
270 render_frame_host_
->GetRoutingID(),
271 default_presentation_url
,
272 default_presentation_id
);
273 default_presentation_url_
= default_presentation_url
;
274 default_presentation_id_
= default_presentation_id
;
277 void PresentationServiceImpl::SetDefaultPresentationURL(
278 const mojo::String
& default_presentation_url
,
279 const mojo::String
& default_presentation_id
) {
280 DVLOG(2) << "SetDefaultPresentationURL";
284 const std::string
& old_default_url
= default_presentation_url_
;
285 const std::string
& new_default_url
= default_presentation_url
.get();
287 // Don't call delegate if nothing changed.
288 if (old_default_url
== new_default_url
&&
289 default_presentation_id_
== default_presentation_id
) {
293 auto old_it
= availability_contexts_
.find(old_default_url
);
294 // Haven't started listening yet.
295 if (old_it
== availability_contexts_
.end()) {
296 DoSetDefaultPresentationUrl(new_default_url
, default_presentation_id
);
300 // Have already started listening. Create a listener for the new URL and
301 // transfer the callbacks from the old listener, if any.
302 // This is done so that a listener added before default URL is changed
303 // will continue to work.
304 ScreenAvailabilityContext
* context
=
305 GetOrCreateAvailabilityContext(new_default_url
);
306 old_it
->second
->PassPendingCallbacks(context
);
308 // Remove listener for old default presentation URL.
309 delegate_
->RemoveScreenAvailabilityListener(
310 render_frame_host_
->GetProcess()->GetID(),
311 render_frame_host_
->GetRoutingID(),
312 old_it
->second
.get());
313 availability_contexts_
.erase(old_it
);
314 DoSetDefaultPresentationUrl(new_default_url
, default_presentation_id
);
317 void PresentationServiceImpl::CloseSession(
318 const mojo::String
& presentation_url
,
319 const mojo::String
& presentation_id
) {
323 void PresentationServiceImpl::ListenForSessionStateChange(
324 const SessionStateCallback
& callback
) {
328 void PresentationServiceImpl::DidNavigateAnyFrame(
329 content::RenderFrameHost
* render_frame_host
,
330 const content::LoadCommittedDetails
& details
,
331 const content::FrameNavigateParams
& params
) {
332 DVLOG(2) << "PresentationServiceImpl::DidNavigateAnyFrame";
333 if (render_frame_host_
!= render_frame_host
)
336 std::string prev_url_host
= details
.previous_url
.host();
337 std::string curr_url_host
= params
.url
.host();
339 // If a frame navigation is in-page (e.g. navigating to a fragment in
340 // same page) then we do not unregister listeners.
341 bool in_page_navigation
= details
.is_in_page
||
342 details
.type
== content::NAVIGATION_TYPE_IN_PAGE
;
344 DVLOG(2) << "DidNavigateAnyFrame: "
345 << "prev host: " << prev_url_host
<< ", curr host: " << curr_url_host
346 << ", in_page_navigation: " << in_page_navigation
;
348 if (in_page_navigation
)
351 // Reset if the frame actually navigated.
355 void PresentationServiceImpl::RenderFrameDeleted(
356 content::RenderFrameHost
* render_frame_host
) {
357 DVLOG(2) << "PresentationServiceImpl::RenderFrameDeleted";
358 if (render_frame_host_
!= render_frame_host
)
361 // RenderFrameDeleted means |render_frame_host_| is going to be deleted soon.
362 // This object should also be deleted.
364 render_frame_host_
= nullptr;
368 void PresentationServiceImpl::Reset() {
369 DVLOG(2) << "PresentationServiceImpl::Reset";
372 render_frame_host_
->GetProcess()->GetID(),
373 render_frame_host_
->GetRoutingID());
376 default_presentation_url_
.clear();
377 default_presentation_id_
.clear();
378 availability_contexts_
.clear();
379 queued_start_session_requests_
.clear();
380 FlushNewSessionCallbacks();
384 void PresentationServiceImpl::InvokeNewSessionMojoCallbackWithError(
385 const NewSessionMojoCallback
& callback
) {
387 presentation::PresentationSessionInfoPtr(),
388 presentation::PresentationError::From(
389 PresentationError(PRESENTATION_ERROR_UNKNOWN
, "Internal error")));
392 void PresentationServiceImpl::OnDelegateDestroyed() {
393 DVLOG(2) << "PresentationServiceImpl::OnDelegateDestroyed";
398 PresentationServiceImpl::ScreenAvailabilityContext::ScreenAvailabilityContext(
399 const std::string
& presentation_url
)
400 : presentation_url_(presentation_url
) {
403 PresentationServiceImpl::ScreenAvailabilityContext::
404 ~ScreenAvailabilityContext() {
405 // Ensure that pending callbacks are flushed.
406 OnScreenAvailabilityChanged(false);
409 void PresentationServiceImpl::ScreenAvailabilityContext::CallbackReceived(
410 const ScreenAvailabilityMojoCallback
& callback
) {
411 // NOTE: This will overwrite previously registered callback if any.
412 if (!available_ptr_
) {
413 // No results yet, store callback for later invocation.
414 callbacks_
.push_back(new ScreenAvailabilityMojoCallback(callback
));
416 // Run callback now, reset result.
417 // There shouldn't be any callbacks stored in this scenario.
418 DCHECK(!HasPendingCallbacks());
419 callback
.Run(presentation_url_
, *available_ptr_
);
420 available_ptr_
.reset();
424 std::string
PresentationServiceImpl::ScreenAvailabilityContext
425 ::GetPresentationUrl() const {
426 return presentation_url_
;
429 void PresentationServiceImpl::ScreenAvailabilityContext
430 ::OnScreenAvailabilityChanged(bool available
) {
431 if (!HasPendingCallbacks()) {
432 // No callback, stash the result for now.
433 available_ptr_
.reset(new bool(available
));
435 // Invoke callbacks and erase them.
436 // There shouldn't be any result stored in this scenario.
437 DCHECK(!available_ptr_
);
438 ScopedVector
<ScreenAvailabilityMojoCallback
> callbacks
;
439 callbacks
.swap(callbacks_
);
440 for (const auto& callback
: callbacks
)
441 callback
->Run(presentation_url_
, available
);
445 void PresentationServiceImpl::ScreenAvailabilityContext
446 ::PassPendingCallbacks(
447 PresentationServiceImpl::ScreenAvailabilityContext
* other
) {
448 std::vector
<ScreenAvailabilityMojoCallback
*> callbacks
;
449 callbacks_
.release(&callbacks
);
450 std::copy(callbacks
.begin(), callbacks
.end(),
451 std::back_inserter(other
->callbacks_
));
454 bool PresentationServiceImpl::ScreenAvailabilityContext
455 ::HasPendingCallbacks() const {
456 return !callbacks_
.empty();
459 PresentationServiceImpl::StartSessionRequest::StartSessionRequest(
460 const std::string
& presentation_url
,
461 const std::string
& presentation_id
,
462 const NewSessionMojoCallback
& callback
)
463 : presentation_url_(presentation_url
),
464 presentation_id_(presentation_id
),
465 callback_(callback
) {
468 PresentationServiceImpl::StartSessionRequest::~StartSessionRequest() {
469 // Ensure that a pending callback is not dropped.
470 if (!callback_
.is_null())
471 InvokeNewSessionMojoCallbackWithError(callback_
);
474 PresentationServiceImpl::NewSessionMojoCallback
475 PresentationServiceImpl::StartSessionRequest::PassCallback() {
476 NewSessionMojoCallback callback
= callback_
;
481 } // namespace content