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 // The return value takes ownership of the contents of |input|.
33 presentation::SessionMessagePtr
ToMojoSessionMessage(
34 content::PresentationSessionMessage
* input
) {
35 presentation::SessionMessagePtr
output(presentation::SessionMessage::New());
36 output
->presentation_url
.Swap(&input
->presentation_url
);
37 output
->presentation_id
.Swap(&input
->presentation_id
);
38 if (input
->is_binary()) {
40 output
->type
= presentation::PresentationMessageType::
41 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
;
42 output
->data
.Swap(input
->data
.get());
46 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT
;
47 output
->message
.Swap(input
->message
.get());
52 scoped_ptr
<content::PresentationSessionMessage
> GetPresentationSessionMessage(
53 presentation::SessionMessagePtr input
) {
54 DCHECK(!input
.is_null());
55 scoped_ptr
<content::PresentationSessionMessage
> output
;
56 switch (input
->type
) {
57 case presentation::PresentationMessageType::
58 PRESENTATION_MESSAGE_TYPE_TEXT
: {
59 DCHECK(!input
->message
.is_null());
60 DCHECK(input
->data
.is_null());
61 // Return null PresentationSessionMessage if size exceeds.
62 if (input
->message
.size() > content::kMaxPresentationSessionMessageSize
)
65 output
= content::PresentationSessionMessage::CreateStringMessage(
66 input
->presentation_url
, input
->presentation_id
,
67 make_scoped_ptr(new std::string
));
68 input
->message
.Swap(output
->message
.get());
71 case presentation::PresentationMessageType::
72 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
: {
73 DCHECK(!input
->data
.is_null());
74 DCHECK(input
->message
.is_null());
75 if (input
->data
.size() > content::kMaxPresentationSessionMessageSize
)
78 output
= content::PresentationSessionMessage::CreateArrayBufferMessage(
79 input
->presentation_url
, input
->presentation_id
,
80 make_scoped_ptr(new std::vector
<uint8_t>));
81 input
->data
.Swap(output
->data
.get());
84 case presentation::PresentationMessageType::
85 PRESENTATION_MESSAGE_TYPE_BLOB
: {
86 DCHECK(!input
->data
.is_null());
87 DCHECK(input
->message
.is_null());
88 if (input
->data
.size() > content::kMaxPresentationSessionMessageSize
)
91 output
= content::PresentationSessionMessage::CreateBlobMessage(
92 input
->presentation_url
, input
->presentation_id
,
93 make_scoped_ptr(new std::vector
<uint8_t>));
94 input
->data
.Swap(output
->data
.get());
99 NOTREACHED() << "Invalid presentation message type " << input
->type
;
100 return output
.Pass();
103 void InvokeNewSessionMojoCallbackWithError(
104 const NewSessionMojoCallback
& callback
) {
106 presentation::PresentationSessionInfoPtr(),
107 presentation::PresentationError::From(
108 PresentationError(PRESENTATION_ERROR_UNKNOWN
, "Internal error")));
113 PresentationServiceImpl::PresentationServiceImpl(
114 RenderFrameHost
* render_frame_host
,
115 WebContents
* web_contents
,
116 PresentationServiceDelegate
* delegate
)
117 : WebContentsObserver(web_contents
),
119 start_session_request_id_(kInvalidRequestSessionId
),
120 weak_factory_(this) {
121 DCHECK(render_frame_host
);
122 DCHECK(web_contents
);
124 render_process_id_
= render_frame_host
->GetProcess()->GetID();
125 render_frame_id_
= render_frame_host
->GetRoutingID();
126 DVLOG(2) << "PresentationServiceImpl: "
127 << render_process_id_
<< ", " << render_frame_id_
;
129 delegate_
->AddObserver(render_process_id_
, render_frame_id_
, this);
132 PresentationServiceImpl::~PresentationServiceImpl() {
134 delegate_
->RemoveObserver(render_process_id_
, render_frame_id_
);
138 void PresentationServiceImpl::CreateMojoService(
139 RenderFrameHost
* render_frame_host
,
140 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
141 DVLOG(2) << "CreateMojoService";
142 WebContents
* web_contents
=
143 WebContents::FromRenderFrameHost(render_frame_host
);
144 DCHECK(web_contents
);
146 // This object will be deleted when the RenderFrameHost is about to be
147 // deleted (RenderFrameDeleted) or if a connection error occurred
148 // (OnConnectionError).
149 PresentationServiceImpl
* impl
= new PresentationServiceImpl(
152 GetContentClient()->browser()->GetPresentationServiceDelegate(
154 impl
->Bind(request
.Pass());
157 void PresentationServiceImpl::Bind(
158 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
159 binding_
.reset(new mojo::Binding
<presentation::PresentationService
>(
160 this, request
.Pass()));
161 binding_
->set_error_handler(this);
164 void PresentationServiceImpl::OnConnectionError() {
165 DVLOG(1) << "OnConnectionError";
169 void PresentationServiceImpl::SetClient(
170 presentation::PresentationServiceClientPtr client
) {
171 DCHECK(!client_
.get());
172 // TODO(imcheng): Set ErrorHandler to listen for errors.
173 client_
= client
.Pass();
176 void PresentationServiceImpl::ListenForScreenAvailability() {
177 DVLOG(2) << "ListenForScreenAvailability";
181 if (screen_availability_listener_
.get() &&
182 screen_availability_listener_
->GetPresentationUrl() ==
183 default_presentation_url_
) {
187 ResetScreenAvailabilityListener(default_presentation_url_
);
190 void PresentationServiceImpl::ResetScreenAvailabilityListener(
191 const std::string
& presentation_url
) {
193 DCHECK(!screen_availability_listener_
.get() ||
194 presentation_url
!= default_presentation_url_
);
196 // (1) Unregister old listener with delegate
197 StopListeningForScreenAvailability();
199 // (2) Replace old listener with new listener
200 screen_availability_listener_
.reset(new ScreenAvailabilityListenerImpl(
201 presentation_url
, this));
203 // (3) Register new listener with delegate
204 if (!delegate_
->AddScreenAvailabilityListener(
207 screen_availability_listener_
.get())) {
208 DVLOG(1) << "AddScreenAvailabilityListener failed. Ignoring request.";
209 screen_availability_listener_
.reset();
213 void PresentationServiceImpl::StopListeningForScreenAvailability() {
214 DVLOG(2) << "StopListeningForScreenAvailability";
218 if (screen_availability_listener_
.get()) {
219 delegate_
->RemoveScreenAvailabilityListener(
222 screen_availability_listener_
.get());
223 screen_availability_listener_
.reset();
227 void PresentationServiceImpl::ListenForDefaultSessionStart(
228 const DefaultSessionMojoCallback
& callback
) {
229 if (!default_session_start_context_
.get())
230 default_session_start_context_
.reset(new DefaultSessionStartContext
);
231 default_session_start_context_
->AddCallback(callback
);
234 void PresentationServiceImpl::StartSession(
235 const mojo::String
& presentation_url
,
236 const NewSessionMojoCallback
& callback
) {
237 DVLOG(2) << "StartSession";
239 InvokeNewSessionMojoCallbackWithError(callback
);
243 // There is a StartSession request in progress. To avoid queueing up
244 // requests, the incoming request is rejected.
245 if (start_session_request_id_
!= kInvalidRequestSessionId
) {
246 InvokeNewSessionMojoCallbackWithError(callback
);
250 start_session_request_id_
= GetNextRequestSessionId();
251 pending_start_session_cb_
.reset(new NewSessionMojoCallbackWrapper(callback
));
252 delegate_
->StartSession(
253 render_process_id_
, render_frame_id_
, presentation_url
,
254 base::Bind(&PresentationServiceImpl::OnStartSessionSucceeded
,
255 weak_factory_
.GetWeakPtr(), start_session_request_id_
),
256 base::Bind(&PresentationServiceImpl::OnStartSessionError
,
257 weak_factory_
.GetWeakPtr(), start_session_request_id_
));
260 void PresentationServiceImpl::JoinSession(
261 const mojo::String
& presentation_url
,
262 const mojo::String
& presentation_id
,
263 const NewSessionMojoCallback
& callback
) {
264 DVLOG(2) << "JoinSession";
266 InvokeNewSessionMojoCallbackWithError(callback
);
270 int request_session_id
= RegisterJoinSessionCallback(callback
);
271 if (request_session_id
== kInvalidRequestSessionId
) {
272 InvokeNewSessionMojoCallbackWithError(callback
);
275 delegate_
->JoinSession(
280 base::Bind(&PresentationServiceImpl::OnJoinSessionSucceeded
,
281 weak_factory_
.GetWeakPtr(), request_session_id
),
282 base::Bind(&PresentationServiceImpl::OnJoinSessionError
,
283 weak_factory_
.GetWeakPtr(), request_session_id
));
286 int PresentationServiceImpl::RegisterJoinSessionCallback(
287 const NewSessionMojoCallback
& callback
) {
288 if (pending_join_session_cbs_
.size() >= kMaxNumQueuedSessionRequests
)
289 return kInvalidRequestSessionId
;
291 int request_id
= GetNextRequestSessionId();
292 pending_join_session_cbs_
[request_id
].reset(
293 new NewSessionMojoCallbackWrapper(callback
));
297 void PresentationServiceImpl::OnStartSessionSucceeded(
298 int request_session_id
,
299 const PresentationSessionInfo
& session_info
) {
300 if (request_session_id
== start_session_request_id_
) {
301 CHECK(pending_start_session_cb_
.get());
302 pending_start_session_cb_
->Run(
303 presentation::PresentationSessionInfo::From(session_info
),
304 presentation::PresentationErrorPtr());
305 pending_start_session_cb_
.reset();
306 start_session_request_id_
= kInvalidRequestSessionId
;
310 void PresentationServiceImpl::OnStartSessionError(
311 int request_session_id
,
312 const PresentationError
& error
) {
313 if (request_session_id
== start_session_request_id_
) {
314 CHECK(pending_start_session_cb_
.get());
315 pending_start_session_cb_
->Run(
316 presentation::PresentationSessionInfoPtr(),
317 presentation::PresentationError::From(error
));
318 pending_start_session_cb_
.reset();
319 start_session_request_id_
= kInvalidRequestSessionId
;
323 void PresentationServiceImpl::OnJoinSessionSucceeded(
324 int request_session_id
,
325 const PresentationSessionInfo
& session_info
) {
326 RunAndEraseJoinSessionMojoCallback(
328 presentation::PresentationSessionInfo::From(session_info
),
329 presentation::PresentationErrorPtr());
332 void PresentationServiceImpl::OnJoinSessionError(
333 int request_session_id
,
334 const PresentationError
& error
) {
335 RunAndEraseJoinSessionMojoCallback(
337 presentation::PresentationSessionInfoPtr(),
338 presentation::PresentationError::From(error
));
341 void PresentationServiceImpl::RunAndEraseJoinSessionMojoCallback(
342 int request_session_id
,
343 presentation::PresentationSessionInfoPtr session
,
344 presentation::PresentationErrorPtr error
) {
345 auto it
= pending_join_session_cbs_
.find(request_session_id
);
346 if (it
== pending_join_session_cbs_
.end())
349 DCHECK(it
->second
.get());
350 it
->second
->Run(session
.Pass(), error
.Pass());
351 pending_join_session_cbs_
.erase(it
);
354 void PresentationServiceImpl::SetDefaultPresentationURL(
355 const mojo::String
& default_presentation_url
) {
356 DVLOG(2) << "SetDefaultPresentationURL";
360 const std::string
& old_default_url
= default_presentation_url_
;
361 const std::string
& new_default_url
= default_presentation_url
.get();
363 if (old_default_url
== new_default_url
)
366 // Replace screen availability listeners if any.
367 if (screen_availability_listener_
.get())
368 ResetScreenAvailabilityListener(new_default_url
);
370 delegate_
->SetDefaultPresentationUrl(
373 default_presentation_url
);
374 default_presentation_url_
= default_presentation_url
;
378 void PresentationServiceImpl::SendSessionMessage(
379 presentation::SessionMessagePtr session_message
,
380 const SendMessageMojoCallback
& callback
) {
381 DVLOG(2) << "SendSessionMessage";
382 DCHECK(!session_message
.is_null());
383 // send_message_callback_ should be null by now, otherwise resetting of
384 // send_message_callback_ with new callback will drop the old callback.
385 if (!delegate_
|| send_message_callback_
) {
390 send_message_callback_
.reset(new SendMessageMojoCallback(callback
));
391 delegate_
->SendMessage(
394 GetPresentationSessionMessage(session_message
.Pass()),
395 base::Bind(&PresentationServiceImpl::OnSendMessageCallback
,
396 weak_factory_
.GetWeakPtr()));
399 void PresentationServiceImpl::OnSendMessageCallback(bool sent
) {
400 // It is possible that Reset() is invoked before receiving this callback.
401 // So, always check send_message_callback_ for non-null.
402 if (send_message_callback_
) {
403 send_message_callback_
->Run(sent
);
404 send_message_callback_
.reset();
408 void PresentationServiceImpl::CloseSession(
409 const mojo::String
& presentation_url
,
410 const mojo::String
& presentation_id
) {
411 DVLOG(2) << "CloseSession " << presentation_id
;
413 delegate_
->CloseSession(render_process_id_
, render_frame_id_
,
417 void PresentationServiceImpl::ListenForSessionStateChange() {
421 delegate_
->ListenForSessionStateChange(
422 render_process_id_
, render_frame_id_
,
423 base::Bind(&PresentationServiceImpl::OnSessionStateChanged
,
424 weak_factory_
.GetWeakPtr()));
427 void PresentationServiceImpl::OnSessionStateChanged(
428 const PresentationSessionInfo
& session_info
,
429 PresentationSessionState session_state
) {
430 DCHECK(client_
.get());
431 client_
->OnSessionStateChanged(
432 presentation::PresentationSessionInfo::From(session_info
),
433 PresentationSessionStateToMojo(session_state
));
436 bool PresentationServiceImpl::FrameMatches(
437 content::RenderFrameHost
* render_frame_host
) const {
438 if (!render_frame_host
)
441 return render_frame_host
->GetProcess()->GetID() == render_process_id_
&&
442 render_frame_host
->GetRoutingID() == render_frame_id_
;
445 void PresentationServiceImpl::ListenForSessionMessages(
446 const SessionMessagesCallback
& callback
) {
447 DVLOG(2) << "ListenForSessionMessages";
449 callback
.Run(mojo::Array
<presentation::SessionMessagePtr
>());
453 // Crash early if renderer is misbehaving.
454 CHECK(!on_session_messages_callback_
.get());
456 on_session_messages_callback_
.reset(new SessionMessagesCallback(callback
));
457 delegate_
->ListenForSessionMessages(
458 render_process_id_
, render_frame_id_
,
459 base::Bind(&PresentationServiceImpl::OnSessionMessages
,
460 weak_factory_
.GetWeakPtr()));
463 void PresentationServiceImpl::OnSessionMessages(
464 scoped_ptr
<ScopedVector
<PresentationSessionMessage
>> messages
) {
465 if (!on_session_messages_callback_
.get()) {
466 // The Reset method of this class was invoked.
470 if (!messages
.get() || messages
->empty()) {
471 // Error handling. Session is either closed or in error state.
472 on_session_messages_callback_
->Run(
473 mojo::Array
<presentation::SessionMessagePtr
>());
475 mojo::Array
<presentation::SessionMessagePtr
> mojoMessages(messages
->size());
476 for (size_t i
= 0; i
< messages
->size(); ++i
) {
477 mojoMessages
[i
] = ToMojoSessionMessage((*messages
)[i
]);
479 on_session_messages_callback_
->Run(mojoMessages
.Pass());
482 on_session_messages_callback_
.reset();
485 void PresentationServiceImpl::DidNavigateAnyFrame(
486 content::RenderFrameHost
* render_frame_host
,
487 const content::LoadCommittedDetails
& details
,
488 const content::FrameNavigateParams
& params
) {
489 DVLOG(2) << "PresentationServiceImpl::DidNavigateAnyFrame";
490 if (!FrameMatches(render_frame_host
))
493 std::string prev_url_host
= details
.previous_url
.host();
494 std::string curr_url_host
= params
.url
.host();
496 // If a frame navigation is in-page (e.g. navigating to a fragment in
497 // same page) then we do not unregister listeners.
498 DVLOG(2) << "DidNavigateAnyFrame: "
499 << "prev host: " << prev_url_host
<< ", curr host: " << curr_url_host
500 << ", details.is_in_page: " << details
.is_in_page
;
501 if (details
.is_in_page
)
504 // Reset if the frame actually navigated.
508 void PresentationServiceImpl::RenderFrameDeleted(
509 content::RenderFrameHost
* render_frame_host
) {
510 DVLOG(2) << "PresentationServiceImpl::RenderFrameDeleted";
511 if (!FrameMatches(render_frame_host
))
514 // RenderFrameDeleted means the associated RFH is going to be deleted soon.
515 // This object should also be deleted.
520 void PresentationServiceImpl::Reset() {
521 DVLOG(2) << "PresentationServiceImpl::Reset";
523 delegate_
->Reset(render_process_id_
, render_frame_id_
);
525 default_presentation_url_
.clear();
527 screen_availability_listener_
.reset();
529 start_session_request_id_
= kInvalidRequestSessionId
;
530 pending_start_session_cb_
.reset();
532 pending_join_session_cbs_
.clear();
534 default_session_start_context_
.reset();
536 if (on_session_messages_callback_
.get()) {
537 on_session_messages_callback_
->Run(
538 mojo::Array
<presentation::SessionMessagePtr
>());
539 on_session_messages_callback_
.reset();
542 if (send_message_callback_
) {
543 // Run the callback with false, indicating the renderer to stop sending
544 // the requests and invalidate all pending requests.
545 send_message_callback_
->Run(false);
546 send_message_callback_
.reset();
550 void PresentationServiceImpl::OnDelegateDestroyed() {
551 DVLOG(2) << "PresentationServiceImpl::OnDelegateDestroyed";
556 void PresentationServiceImpl::OnDefaultPresentationStarted(
557 const PresentationSessionInfo
& session
) {
558 if (default_session_start_context_
.get())
559 default_session_start_context_
->set_session(session
);
562 PresentationServiceImpl::ScreenAvailabilityListenerImpl
563 ::ScreenAvailabilityListenerImpl(
564 const std::string
& presentation_url
,
565 PresentationServiceImpl
* service
)
566 : presentation_url_(presentation_url
),
569 DCHECK(service_
->client_
.get());
572 PresentationServiceImpl::ScreenAvailabilityListenerImpl::
573 ~ScreenAvailabilityListenerImpl() {
576 std::string
PresentationServiceImpl::ScreenAvailabilityListenerImpl
577 ::GetPresentationUrl() const {
578 return presentation_url_
;
581 void PresentationServiceImpl::ScreenAvailabilityListenerImpl
582 ::OnScreenAvailabilityChanged(bool available
) {
583 service_
->client_
->OnScreenAvailabilityUpdated(available
);
586 PresentationServiceImpl::NewSessionMojoCallbackWrapper
587 ::NewSessionMojoCallbackWrapper(const NewSessionMojoCallback
& callback
)
588 : callback_(callback
) {
591 PresentationServiceImpl::NewSessionMojoCallbackWrapper
592 ::~NewSessionMojoCallbackWrapper() {
593 if (!callback_
.is_null())
594 InvokeNewSessionMojoCallbackWithError(callback_
);
597 void PresentationServiceImpl::NewSessionMojoCallbackWrapper::Run(
598 presentation::PresentationSessionInfoPtr session
,
599 presentation::PresentationErrorPtr error
) {
600 DCHECK(!callback_
.is_null());
601 callback_
.Run(session
.Pass(), error
.Pass());
605 PresentationServiceImpl::DefaultSessionStartContext
606 ::DefaultSessionStartContext() {
609 PresentationServiceImpl::DefaultSessionStartContext
610 ::~DefaultSessionStartContext() {
614 void PresentationServiceImpl::DefaultSessionStartContext::AddCallback(
615 const DefaultSessionMojoCallback
& callback
) {
616 if (session_
.get()) {
617 DCHECK(callbacks_
.empty());
618 callback
.Run(presentation::PresentationSessionInfo::From(*session_
));
621 callbacks_
.push_back(new DefaultSessionMojoCallback(callback
));
625 void PresentationServiceImpl::DefaultSessionStartContext::set_session(
626 const PresentationSessionInfo
& session
) {
627 if (callbacks_
.empty()) {
628 session_
.reset(new PresentationSessionInfo(session
));
630 DCHECK(!session_
.get());
631 ScopedVector
<DefaultSessionMojoCallback
> callbacks
;
632 callbacks
.swap(callbacks_
);
633 for (const auto& callback
: callbacks
)
634 callback
->Run(presentation::PresentationSessionInfo::From(session
));
638 void PresentationServiceImpl::DefaultSessionStartContext::Reset() {
639 ScopedVector
<DefaultSessionMojoCallback
> callbacks
;
640 callbacks
.swap(callbacks_
);
641 for (const auto& callback
: callbacks
)
642 callback
->Run(presentation::PresentationSessionInfoPtr());
646 } // namespace content