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/presentation_session_message.h"
14 #include "content/public/browser/render_frame_host.h"
15 #include "content/public/browser/render_process_host.h"
16 #include "content/public/browser/web_contents.h"
17 #include "content/public/common/content_client.h"
18 #include "content/public/common/frame_navigate_params.h"
19 #include "content/public/common/presentation_constants.h"
25 const int kInvalidRequestSessionId
= -1;
27 int GetNextRequestSessionId() {
28 static int next_request_session_id
= 0;
29 return ++next_request_session_id
;
32 presentation::SessionMessagePtr
ToMojoSessionMessage(
33 const content::PresentationSessionMessage
& input
) {
34 presentation::SessionMessagePtr
output(presentation::SessionMessage::New());
35 if (input
.is_binary()) {
37 output
->type
= presentation::PresentationMessageType::
38 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
;
39 output
->data
= mojo::Array
<uint8_t>::From(*input
.data
);
43 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT
;
44 output
->message
= input
.message
;
49 scoped_ptr
<PresentationSessionMessage
> GetPresentationSessionMessage(
50 presentation::SessionMessagePtr input
) {
51 DCHECK(!input
.is_null());
52 scoped_ptr
<content::PresentationSessionMessage
> output
;
53 switch (input
->type
) {
54 case presentation::PRESENTATION_MESSAGE_TYPE_TEXT
: {
55 DCHECK(!input
->message
.is_null());
56 DCHECK(input
->data
.is_null());
57 // Return null PresentationSessionMessage if size exceeds.
58 if (input
->message
.size() > content::kMaxPresentationSessionMessageSize
)
62 new PresentationSessionMessage(PresentationMessageType::TEXT
));
63 input
->message
.Swap(&output
->message
);
66 case presentation::PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
: {
67 DCHECK(!input
->data
.is_null());
68 DCHECK(input
->message
.is_null());
69 if (input
->data
.size() > content::kMaxPresentationSessionMessageSize
)
72 output
.reset(new PresentationSessionMessage(
73 PresentationMessageType::ARRAY_BUFFER
));
74 output
->data
.reset(new std::vector
<uint8_t>);
75 input
->data
.Swap(output
->data
.get());
78 case presentation::PRESENTATION_MESSAGE_TYPE_BLOB
: {
79 DCHECK(!input
->data
.is_null());
80 DCHECK(input
->message
.is_null());
81 if (input
->data
.size() > content::kMaxPresentationSessionMessageSize
)
85 new PresentationSessionMessage(PresentationMessageType::BLOB
));
86 output
->data
.reset(new std::vector
<uint8_t>);
87 input
->data
.Swap(output
->data
.get());
92 NOTREACHED() << "Invalid presentation message type " << input
->type
;
96 void InvokeNewSessionMojoCallbackWithError(
97 const NewSessionMojoCallback
& callback
) {
99 presentation::PresentationSessionInfoPtr(),
100 presentation::PresentationError::From(
101 PresentationError(PRESENTATION_ERROR_UNKNOWN
, "Internal error")));
106 PresentationServiceImpl::PresentationServiceImpl(
107 RenderFrameHost
* render_frame_host
,
108 WebContents
* web_contents
,
109 PresentationServiceDelegate
* delegate
)
110 : WebContentsObserver(web_contents
),
112 start_session_request_id_(kInvalidRequestSessionId
),
113 weak_factory_(this) {
114 DCHECK(render_frame_host
);
115 DCHECK(web_contents
);
117 render_process_id_
= render_frame_host
->GetProcess()->GetID();
118 render_frame_id_
= render_frame_host
->GetRoutingID();
119 DVLOG(2) << "PresentationServiceImpl: "
120 << render_process_id_
<< ", " << render_frame_id_
;
122 delegate_
->AddObserver(render_process_id_
, render_frame_id_
, this);
125 PresentationServiceImpl::~PresentationServiceImpl() {
127 delegate_
->RemoveObserver(render_process_id_
, render_frame_id_
);
131 void PresentationServiceImpl::CreateMojoService(
132 RenderFrameHost
* render_frame_host
,
133 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
134 DVLOG(2) << "CreateMojoService";
135 WebContents
* web_contents
=
136 WebContents::FromRenderFrameHost(render_frame_host
);
137 DCHECK(web_contents
);
139 // This object will be deleted when the RenderFrameHost is about to be
140 // deleted (RenderFrameDeleted) or if a connection error occurred
141 // (OnConnectionError).
142 PresentationServiceImpl
* impl
= new PresentationServiceImpl(
145 GetContentClient()->browser()->GetPresentationServiceDelegate(
147 impl
->Bind(request
.Pass());
150 void PresentationServiceImpl::Bind(
151 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
152 binding_
.reset(new mojo::Binding
<presentation::PresentationService
>(
153 this, request
.Pass()));
154 binding_
->set_error_handler(this);
157 void PresentationServiceImpl::OnConnectionError() {
158 DVLOG(1) << "OnConnectionError";
162 void PresentationServiceImpl::SetClient(
163 presentation::PresentationServiceClientPtr client
) {
164 DCHECK(!client_
.get());
165 // TODO(imcheng): Set ErrorHandler to listen for errors.
166 client_
= client
.Pass();
169 void PresentationServiceImpl::ListenForScreenAvailability() {
170 DVLOG(2) << "ListenForScreenAvailability";
174 if (screen_availability_listener_
.get() &&
175 screen_availability_listener_
->GetPresentationUrl() ==
176 default_presentation_url_
) {
180 ResetScreenAvailabilityListener(default_presentation_url_
);
183 void PresentationServiceImpl::ResetScreenAvailabilityListener(
184 const std::string
& presentation_url
) {
186 DCHECK(!screen_availability_listener_
.get() ||
187 presentation_url
!= default_presentation_url_
);
189 // (1) Unregister old listener with delegate
190 StopListeningForScreenAvailability();
192 // (2) Replace old listener with new listener
193 screen_availability_listener_
.reset(new ScreenAvailabilityListenerImpl(
194 presentation_url
, this));
196 // (3) Register new listener with delegate
197 if (!delegate_
->AddScreenAvailabilityListener(
200 screen_availability_listener_
.get())) {
201 DVLOG(1) << "AddScreenAvailabilityListener failed. Ignoring request.";
202 screen_availability_listener_
.reset();
206 void PresentationServiceImpl::StopListeningForScreenAvailability() {
207 DVLOG(2) << "StopListeningForScreenAvailability";
211 if (screen_availability_listener_
.get()) {
212 delegate_
->RemoveScreenAvailabilityListener(
215 screen_availability_listener_
.get());
216 screen_availability_listener_
.reset();
220 void PresentationServiceImpl::ListenForDefaultSessionStart(
221 const DefaultSessionMojoCallback
& callback
) {
222 if (!default_session_start_context_
.get())
223 default_session_start_context_
.reset(new DefaultSessionStartContext
);
224 default_session_start_context_
->AddCallback(callback
);
227 void PresentationServiceImpl::StartSession(
228 const mojo::String
& presentation_url
,
229 const NewSessionMojoCallback
& callback
) {
230 DVLOG(2) << "StartSession";
232 InvokeNewSessionMojoCallbackWithError(callback
);
236 // There is a StartSession request in progress. To avoid queueing up
237 // requests, the incoming request is rejected.
238 if (start_session_request_id_
!= kInvalidRequestSessionId
) {
239 InvokeNewSessionMojoCallbackWithError(callback
);
243 start_session_request_id_
= GetNextRequestSessionId();
244 pending_start_session_cb_
.reset(new NewSessionMojoCallbackWrapper(callback
));
245 delegate_
->StartSession(
246 render_process_id_
, render_frame_id_
, presentation_url
,
247 base::Bind(&PresentationServiceImpl::OnStartSessionSucceeded
,
248 weak_factory_
.GetWeakPtr(), start_session_request_id_
),
249 base::Bind(&PresentationServiceImpl::OnStartSessionError
,
250 weak_factory_
.GetWeakPtr(), start_session_request_id_
));
253 void PresentationServiceImpl::JoinSession(
254 const mojo::String
& presentation_url
,
255 const mojo::String
& presentation_id
,
256 const NewSessionMojoCallback
& callback
) {
257 DVLOG(2) << "JoinSession";
259 InvokeNewSessionMojoCallbackWithError(callback
);
263 int request_session_id
= RegisterJoinSessionCallback(callback
);
264 if (request_session_id
== kInvalidRequestSessionId
) {
265 InvokeNewSessionMojoCallbackWithError(callback
);
268 delegate_
->JoinSession(
273 base::Bind(&PresentationServiceImpl::OnJoinSessionSucceeded
,
274 weak_factory_
.GetWeakPtr(), request_session_id
),
275 base::Bind(&PresentationServiceImpl::OnJoinSessionError
,
276 weak_factory_
.GetWeakPtr(), request_session_id
));
279 int PresentationServiceImpl::RegisterJoinSessionCallback(
280 const NewSessionMojoCallback
& callback
) {
281 if (pending_join_session_cbs_
.size() >= kMaxNumQueuedSessionRequests
)
282 return kInvalidRequestSessionId
;
284 int request_id
= GetNextRequestSessionId();
285 pending_join_session_cbs_
[request_id
].reset(
286 new NewSessionMojoCallbackWrapper(callback
));
290 void PresentationServiceImpl::OnStartSessionSucceeded(
291 int request_session_id
,
292 const PresentationSessionInfo
& session_info
) {
293 if (request_session_id
== start_session_request_id_
) {
294 CHECK(pending_start_session_cb_
.get());
295 pending_start_session_cb_
->Run(
296 presentation::PresentationSessionInfo::From(session_info
),
297 presentation::PresentationErrorPtr());
298 pending_start_session_cb_
.reset();
299 start_session_request_id_
= kInvalidRequestSessionId
;
303 void PresentationServiceImpl::OnStartSessionError(
304 int request_session_id
,
305 const PresentationError
& error
) {
306 if (request_session_id
== start_session_request_id_
) {
307 CHECK(pending_start_session_cb_
.get());
308 pending_start_session_cb_
->Run(
309 presentation::PresentationSessionInfoPtr(),
310 presentation::PresentationError::From(error
));
311 pending_start_session_cb_
.reset();
312 start_session_request_id_
= kInvalidRequestSessionId
;
316 void PresentationServiceImpl::OnJoinSessionSucceeded(
317 int request_session_id
,
318 const PresentationSessionInfo
& session_info
) {
319 RunAndEraseJoinSessionMojoCallback(
321 presentation::PresentationSessionInfo::From(session_info
),
322 presentation::PresentationErrorPtr());
325 void PresentationServiceImpl::OnJoinSessionError(
326 int request_session_id
,
327 const PresentationError
& error
) {
328 RunAndEraseJoinSessionMojoCallback(
330 presentation::PresentationSessionInfoPtr(),
331 presentation::PresentationError::From(error
));
334 void PresentationServiceImpl::RunAndEraseJoinSessionMojoCallback(
335 int request_session_id
,
336 presentation::PresentationSessionInfoPtr session
,
337 presentation::PresentationErrorPtr error
) {
338 auto it
= pending_join_session_cbs_
.find(request_session_id
);
339 if (it
== pending_join_session_cbs_
.end())
342 DCHECK(it
->second
.get());
343 it
->second
->Run(session
.Pass(), error
.Pass());
344 pending_join_session_cbs_
.erase(it
);
347 void PresentationServiceImpl::SetDefaultPresentationURL(
348 const mojo::String
& default_presentation_url
) {
349 DVLOG(2) << "SetDefaultPresentationURL";
353 const std::string
& old_default_url
= default_presentation_url_
;
354 const std::string
& new_default_url
= default_presentation_url
.get();
356 if (old_default_url
== new_default_url
)
359 // Replace screen availability listeners if any.
360 if (screen_availability_listener_
.get())
361 ResetScreenAvailabilityListener(new_default_url
);
363 delegate_
->SetDefaultPresentationUrl(
366 default_presentation_url
);
367 default_presentation_url_
= default_presentation_url
;
370 void PresentationServiceImpl::SendSessionMessage(
371 presentation::PresentationSessionInfoPtr session
,
372 presentation::SessionMessagePtr session_message
,
373 const SendMessageMojoCallback
& callback
) {
374 DVLOG(2) << "SendSessionMessage";
375 DCHECK(!session_message
.is_null());
376 // send_message_callback_ should be null by now, otherwise resetting of
377 // send_message_callback_ with new callback will drop the old callback.
378 if (!delegate_
|| send_message_callback_
) {
383 send_message_callback_
.reset(new SendMessageMojoCallback(callback
));
384 delegate_
->SendMessage(
385 render_process_id_
, render_frame_id_
,
386 session
.To
<PresentationSessionInfo
>(),
387 GetPresentationSessionMessage(session_message
.Pass()),
388 base::Bind(&PresentationServiceImpl::OnSendMessageCallback
,
389 weak_factory_
.GetWeakPtr()));
392 void PresentationServiceImpl::OnSendMessageCallback(bool sent
) {
393 // It is possible that Reset() is invoked before receiving this callback.
394 // So, always check send_message_callback_ for non-null.
395 if (send_message_callback_
) {
396 send_message_callback_
->Run(sent
);
397 send_message_callback_
.reset();
401 void PresentationServiceImpl::CloseSession(
402 const mojo::String
& presentation_url
,
403 const mojo::String
& presentation_id
) {
404 DVLOG(2) << "CloseSession " << presentation_id
;
406 delegate_
->CloseSession(render_process_id_
, render_frame_id_
,
410 void PresentationServiceImpl::ListenForSessionStateChange() {
414 delegate_
->ListenForSessionStateChange(
415 render_process_id_
, render_frame_id_
,
416 base::Bind(&PresentationServiceImpl::OnSessionStateChanged
,
417 weak_factory_
.GetWeakPtr()));
420 void PresentationServiceImpl::OnSessionStateChanged(
421 const PresentationSessionInfo
& session_info
,
422 PresentationSessionState session_state
) {
423 DCHECK(client_
.get());
424 client_
->OnSessionStateChanged(
425 presentation::PresentationSessionInfo::From(session_info
),
426 PresentationSessionStateToMojo(session_state
));
429 bool PresentationServiceImpl::FrameMatches(
430 content::RenderFrameHost
* render_frame_host
) const {
431 if (!render_frame_host
)
434 return render_frame_host
->GetProcess()->GetID() == render_process_id_
&&
435 render_frame_host
->GetRoutingID() == render_frame_id_
;
438 void PresentationServiceImpl::ListenForSessionMessages(
439 presentation::PresentationSessionInfoPtr session
) {
440 DVLOG(2) << "ListenForSessionMessages";
444 PresentationSessionInfo
session_info(session
.To
<PresentationSessionInfo
>());
445 delegate_
->ListenForSessionMessages(
446 render_process_id_
, render_frame_id_
, session_info
,
447 base::Bind(&PresentationServiceImpl::OnSessionMessages
,
448 weak_factory_
.GetWeakPtr(), session_info
));
451 void PresentationServiceImpl::OnSessionMessages(
452 const PresentationSessionInfo
& session
,
453 const ScopedVector
<PresentationSessionMessage
>& messages
) {
456 DVLOG(2) << "OnSessionMessages";
457 mojo::Array
<presentation::SessionMessagePtr
> mojoMessages(messages
.size());
458 for (size_t i
= 0; i
< messages
.size(); ++i
)
459 mojoMessages
[i
] = ToMojoSessionMessage(*messages
[i
]);
461 client_
->OnSessionMessagesReceived(
462 presentation::PresentationSessionInfo::From(session
),
463 mojoMessages
.Pass());
466 void PresentationServiceImpl::DidNavigateAnyFrame(
467 content::RenderFrameHost
* render_frame_host
,
468 const content::LoadCommittedDetails
& details
,
469 const content::FrameNavigateParams
& params
) {
470 DVLOG(2) << "PresentationServiceImpl::DidNavigateAnyFrame";
471 if (!FrameMatches(render_frame_host
))
474 std::string prev_url_host
= details
.previous_url
.host();
475 std::string curr_url_host
= params
.url
.host();
477 // If a frame navigation is in-page (e.g. navigating to a fragment in
478 // same page) then we do not unregister listeners.
479 DVLOG(2) << "DidNavigateAnyFrame: "
480 << "prev host: " << prev_url_host
<< ", curr host: " << curr_url_host
481 << ", details.is_in_page: " << details
.is_in_page
;
482 if (details
.is_in_page
)
485 // Reset if the frame actually navigated.
489 void PresentationServiceImpl::RenderFrameDeleted(
490 content::RenderFrameHost
* render_frame_host
) {
491 DVLOG(2) << "PresentationServiceImpl::RenderFrameDeleted";
492 if (!FrameMatches(render_frame_host
))
495 // RenderFrameDeleted means the associated RFH is going to be deleted soon.
496 // This object should also be deleted.
501 void PresentationServiceImpl::Reset() {
502 DVLOG(2) << "PresentationServiceImpl::Reset";
504 delegate_
->Reset(render_process_id_
, render_frame_id_
);
506 default_presentation_url_
.clear();
508 screen_availability_listener_
.reset();
510 start_session_request_id_
= kInvalidRequestSessionId
;
511 pending_start_session_cb_
.reset();
513 pending_join_session_cbs_
.clear();
515 default_session_start_context_
.reset();
517 if (on_session_messages_callback_
.get()) {
518 on_session_messages_callback_
->Run(
519 mojo::Array
<presentation::SessionMessagePtr
>());
520 on_session_messages_callback_
.reset();
523 if (send_message_callback_
) {
524 // Run the callback with false, indicating the renderer to stop sending
525 // the requests and invalidate all pending requests.
526 send_message_callback_
->Run(false);
527 send_message_callback_
.reset();
531 void PresentationServiceImpl::OnDelegateDestroyed() {
532 DVLOG(2) << "PresentationServiceImpl::OnDelegateDestroyed";
537 void PresentationServiceImpl::OnDefaultPresentationStarted(
538 const PresentationSessionInfo
& session
) {
539 if (default_session_start_context_
.get())
540 default_session_start_context_
->set_session(session
);
543 PresentationServiceImpl::ScreenAvailabilityListenerImpl
544 ::ScreenAvailabilityListenerImpl(
545 const std::string
& presentation_url
,
546 PresentationServiceImpl
* service
)
547 : presentation_url_(presentation_url
),
550 DCHECK(service_
->client_
.get());
553 PresentationServiceImpl::ScreenAvailabilityListenerImpl::
554 ~ScreenAvailabilityListenerImpl() {
557 std::string
PresentationServiceImpl::ScreenAvailabilityListenerImpl
558 ::GetPresentationUrl() const {
559 return presentation_url_
;
562 void PresentationServiceImpl::ScreenAvailabilityListenerImpl
563 ::OnScreenAvailabilityChanged(bool available
) {
564 service_
->client_
->OnScreenAvailabilityUpdated(available
);
567 PresentationServiceImpl::NewSessionMojoCallbackWrapper
568 ::NewSessionMojoCallbackWrapper(const NewSessionMojoCallback
& callback
)
569 : callback_(callback
) {
572 PresentationServiceImpl::NewSessionMojoCallbackWrapper
573 ::~NewSessionMojoCallbackWrapper() {
574 if (!callback_
.is_null())
575 InvokeNewSessionMojoCallbackWithError(callback_
);
578 void PresentationServiceImpl::NewSessionMojoCallbackWrapper::Run(
579 presentation::PresentationSessionInfoPtr session
,
580 presentation::PresentationErrorPtr error
) {
581 DCHECK(!callback_
.is_null());
582 callback_
.Run(session
.Pass(), error
.Pass());
586 PresentationServiceImpl::DefaultSessionStartContext
587 ::DefaultSessionStartContext() {
590 PresentationServiceImpl::DefaultSessionStartContext
591 ::~DefaultSessionStartContext() {
595 void PresentationServiceImpl::DefaultSessionStartContext::AddCallback(
596 const DefaultSessionMojoCallback
& callback
) {
597 if (session_
.get()) {
598 DCHECK(callbacks_
.empty());
599 callback
.Run(presentation::PresentationSessionInfo::From(*session_
));
602 callbacks_
.push_back(new DefaultSessionMojoCallback(callback
));
606 void PresentationServiceImpl::DefaultSessionStartContext::set_session(
607 const PresentationSessionInfo
& session
) {
608 if (callbacks_
.empty()) {
609 session_
.reset(new PresentationSessionInfo(session
));
611 DCHECK(!session_
.get());
612 ScopedVector
<DefaultSessionMojoCallback
> callbacks
;
613 callbacks
.swap(callbacks_
);
614 for (const auto& callback
: callbacks
)
615 callback
->Run(presentation::PresentationSessionInfo::From(session
));
619 void PresentationServiceImpl::DefaultSessionStartContext::Reset() {
620 ScopedVector
<DefaultSessionMojoCallback
> callbacks
;
621 callbacks
.swap(callbacks_
);
622 for (const auto& callback
: callbacks
)
623 callback
->Run(presentation::PresentationSessionInfoPtr());
627 } // namespace content