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"
11 #include "base/logging.h"
12 #include "base/stl_util.h"
13 #include "content/browser/presentation/presentation_type_converters.h"
14 #include "content/public/browser/content_browser_client.h"
15 #include "content/public/browser/navigation_details.h"
16 #include "content/public/browser/presentation_session_message.h"
17 #include "content/public/browser/render_frame_host.h"
18 #include "content/public/browser/render_process_host.h"
19 #include "content/public/browser/web_contents.h"
20 #include "content/public/common/content_client.h"
21 #include "content/public/common/frame_navigate_params.h"
22 #include "content/public/common/presentation_constants.h"
28 const int kInvalidRequestSessionId
= -1;
30 int GetNextRequestSessionId() {
31 static int next_request_session_id
= 0;
32 return ++next_request_session_id
;
35 // Converts a PresentationSessionMessage |input| to a SessionMessage.
36 // |input|: The message to convert.
37 // |pass_ownership|: If true, function may reuse strings or buffers from
38 // |input| without copying. |input| can be freely modified.
39 presentation::SessionMessagePtr
ToMojoSessionMessage(
40 content::PresentationSessionMessage
* input
,
41 bool pass_ownership
) {
43 presentation::SessionMessagePtr
output(presentation::SessionMessage::New());
44 if (input
->is_binary()) {
47 output
->type
= presentation::PresentationMessageType::
48 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
;
50 output
->data
.Swap(input
->data
.get());
52 output
->data
= mojo::Array
<uint8_t>::From(*input
->data
);
57 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT
;
59 output
->message
.Swap(&input
->message
);
61 output
->message
= input
->message
;
67 scoped_ptr
<PresentationSessionMessage
> GetPresentationSessionMessage(
68 presentation::SessionMessagePtr input
) {
69 DCHECK(!input
.is_null());
70 scoped_ptr
<content::PresentationSessionMessage
> output
;
71 switch (input
->type
) {
72 case presentation::PRESENTATION_MESSAGE_TYPE_TEXT
: {
73 DCHECK(!input
->message
.is_null());
74 DCHECK(input
->data
.is_null());
75 // Return null PresentationSessionMessage if size exceeds.
76 if (input
->message
.size() > content::kMaxPresentationSessionMessageSize
)
80 new PresentationSessionMessage(PresentationMessageType::TEXT
));
81 input
->message
.Swap(&output
->message
);
84 case presentation::PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
: {
85 DCHECK(!input
->data
.is_null());
86 DCHECK(input
->message
.is_null());
87 if (input
->data
.size() > content::kMaxPresentationSessionMessageSize
)
90 output
.reset(new PresentationSessionMessage(
91 PresentationMessageType::ARRAY_BUFFER
));
92 output
->data
.reset(new std::vector
<uint8_t>);
93 input
->data
.Swap(output
->data
.get());
96 case presentation::PRESENTATION_MESSAGE_TYPE_BLOB
: {
97 DCHECK(!input
->data
.is_null());
98 DCHECK(input
->message
.is_null());
99 if (input
->data
.size() > content::kMaxPresentationSessionMessageSize
)
100 return output
.Pass();
103 new PresentationSessionMessage(PresentationMessageType::BLOB
));
104 output
->data
.reset(new std::vector
<uint8_t>);
105 input
->data
.Swap(output
->data
.get());
106 return output
.Pass();
110 NOTREACHED() << "Invalid presentation message type " << input
->type
;
111 return output
.Pass();
114 void InvokeNewSessionMojoCallbackWithError(
115 const NewSessionMojoCallback
& callback
) {
117 presentation::PresentationSessionInfoPtr(),
118 presentation::PresentationError::From(
119 PresentationError(PRESENTATION_ERROR_UNKNOWN
, "Internal error")));
124 PresentationServiceImpl::PresentationServiceImpl(
125 RenderFrameHost
* render_frame_host
,
126 WebContents
* web_contents
,
127 PresentationServiceDelegate
* delegate
)
128 : WebContentsObserver(web_contents
),
130 start_session_request_id_(kInvalidRequestSessionId
),
131 weak_factory_(this) {
132 DCHECK(render_frame_host
);
133 DCHECK(web_contents
);
135 render_process_id_
= render_frame_host
->GetProcess()->GetID();
136 render_frame_id_
= render_frame_host
->GetRoutingID();
137 DVLOG(2) << "PresentationServiceImpl: "
138 << render_process_id_
<< ", " << render_frame_id_
;
140 delegate_
->AddObserver(render_process_id_
, render_frame_id_
, this);
143 PresentationServiceImpl::~PresentationServiceImpl() {
145 delegate_
->RemoveObserver(render_process_id_
, render_frame_id_
);
149 void PresentationServiceImpl::CreateMojoService(
150 RenderFrameHost
* render_frame_host
,
151 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
152 DVLOG(2) << "CreateMojoService";
153 WebContents
* web_contents
=
154 WebContents::FromRenderFrameHost(render_frame_host
);
155 DCHECK(web_contents
);
157 // This object will be deleted when the RenderFrameHost is about to be
158 // deleted (RenderFrameDeleted) or if a connection error occurred
159 // (OnConnectionError).
160 PresentationServiceImpl
* impl
= new PresentationServiceImpl(
163 GetContentClient()->browser()->GetPresentationServiceDelegate(
165 impl
->Bind(request
.Pass());
168 void PresentationServiceImpl::Bind(
169 mojo::InterfaceRequest
<presentation::PresentationService
> request
) {
170 binding_
.reset(new mojo::Binding
<presentation::PresentationService
>(
171 this, request
.Pass()));
172 binding_
->set_connection_error_handler([this]() {
173 DVLOG(1) << "Connection error";
178 void PresentationServiceImpl::SetClient(
179 presentation::PresentationServiceClientPtr client
) {
180 DCHECK(!client_
.get());
181 // TODO(imcheng): Set ErrorHandler to listen for errors.
182 client_
= client
.Pass();
185 void PresentationServiceImpl::ListenForScreenAvailability(
186 const mojo::String
& url
) {
187 DVLOG(2) << "ListenForScreenAvailability " << url
;
189 client_
->OnScreenAvailabilityUpdated(url
, false);
193 const std::string
& availability_url
= url
.get();
194 if (screen_availability_listeners_
.count(availability_url
))
197 scoped_ptr
<ScreenAvailabilityListenerImpl
> listener(
198 new ScreenAvailabilityListenerImpl(availability_url
, this));
199 if (delegate_
->AddScreenAvailabilityListener(
203 screen_availability_listeners_
.set(availability_url
, listener
.Pass());
205 DVLOG(1) << "AddScreenAvailabilityListener failed. Ignoring request.";
209 void PresentationServiceImpl::StopListeningForScreenAvailability(
210 const mojo::String
& url
) {
211 DVLOG(2) << "StopListeningForScreenAvailability " << url
;
215 const std::string
& availability_url
= url
.get();
216 auto listener_it
= screen_availability_listeners_
.find(availability_url
);
217 if (listener_it
== screen_availability_listeners_
.end())
220 delegate_
->RemoveScreenAvailabilityListener(
223 listener_it
->second
);
224 screen_availability_listeners_
.erase(listener_it
);
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";
240 presentation::PresentationSessionInfoPtr(),
241 presentation::PresentationError::From(
242 PresentationError(PRESENTATION_ERROR_NO_AVAILABLE_SCREENS
,
243 "No screens found.")));
247 // There is a StartSession request in progress. To avoid queueing up
248 // requests, the incoming request is rejected.
249 if (start_session_request_id_
!= kInvalidRequestSessionId
) {
250 InvokeNewSessionMojoCallbackWithError(callback
);
254 start_session_request_id_
= GetNextRequestSessionId();
255 pending_start_session_cb_
.reset(new NewSessionMojoCallbackWrapper(callback
));
256 delegate_
->StartSession(
257 render_process_id_
, render_frame_id_
, presentation_url
,
258 base::Bind(&PresentationServiceImpl::OnStartSessionSucceeded
,
259 weak_factory_
.GetWeakPtr(), start_session_request_id_
),
260 base::Bind(&PresentationServiceImpl::OnStartSessionError
,
261 weak_factory_
.GetWeakPtr(), start_session_request_id_
));
264 void PresentationServiceImpl::JoinSession(
265 const mojo::String
& presentation_url
,
266 const mojo::String
& presentation_id
,
267 const NewSessionMojoCallback
& callback
) {
268 DVLOG(2) << "JoinSession";
271 presentation::PresentationSessionInfoPtr(),
272 presentation::PresentationError::From(
273 PresentationError(PRESENTATION_ERROR_NO_PRESENTATION_FOUND
,
274 "Error joining route: No matching route")));
278 int request_session_id
= RegisterJoinSessionCallback(callback
);
279 if (request_session_id
== kInvalidRequestSessionId
) {
280 InvokeNewSessionMojoCallbackWithError(callback
);
283 delegate_
->JoinSession(
288 base::Bind(&PresentationServiceImpl::OnJoinSessionSucceeded
,
289 weak_factory_
.GetWeakPtr(), request_session_id
),
290 base::Bind(&PresentationServiceImpl::OnJoinSessionError
,
291 weak_factory_
.GetWeakPtr(), request_session_id
));
294 int PresentationServiceImpl::RegisterJoinSessionCallback(
295 const NewSessionMojoCallback
& callback
) {
296 if (pending_join_session_cbs_
.size() >= kMaxNumQueuedSessionRequests
)
297 return kInvalidRequestSessionId
;
299 int request_id
= GetNextRequestSessionId();
300 pending_join_session_cbs_
[request_id
].reset(
301 new NewSessionMojoCallbackWrapper(callback
));
305 void PresentationServiceImpl::OnStartSessionSucceeded(
306 int request_session_id
,
307 const PresentationSessionInfo
& session_info
) {
308 if (request_session_id
== start_session_request_id_
) {
309 CHECK(pending_start_session_cb_
.get());
310 pending_start_session_cb_
->Run(
311 presentation::PresentationSessionInfo::From(session_info
),
312 presentation::PresentationErrorPtr());
313 pending_start_session_cb_
.reset();
314 start_session_request_id_
= kInvalidRequestSessionId
;
318 void PresentationServiceImpl::OnStartSessionError(
319 int request_session_id
,
320 const PresentationError
& error
) {
321 if (request_session_id
== start_session_request_id_
) {
322 CHECK(pending_start_session_cb_
.get());
323 pending_start_session_cb_
->Run(
324 presentation::PresentationSessionInfoPtr(),
325 presentation::PresentationError::From(error
));
326 pending_start_session_cb_
.reset();
327 start_session_request_id_
= kInvalidRequestSessionId
;
331 void PresentationServiceImpl::OnJoinSessionSucceeded(
332 int request_session_id
,
333 const PresentationSessionInfo
& session_info
) {
334 RunAndEraseJoinSessionMojoCallback(
336 presentation::PresentationSessionInfo::From(session_info
),
337 presentation::PresentationErrorPtr());
340 void PresentationServiceImpl::OnJoinSessionError(
341 int request_session_id
,
342 const PresentationError
& error
) {
343 RunAndEraseJoinSessionMojoCallback(
345 presentation::PresentationSessionInfoPtr(),
346 presentation::PresentationError::From(error
));
349 void PresentationServiceImpl::RunAndEraseJoinSessionMojoCallback(
350 int request_session_id
,
351 presentation::PresentationSessionInfoPtr session
,
352 presentation::PresentationErrorPtr error
) {
353 auto it
= pending_join_session_cbs_
.find(request_session_id
);
354 if (it
== pending_join_session_cbs_
.end())
357 DCHECK(it
->second
.get());
358 it
->second
->Run(session
.Pass(), error
.Pass());
359 pending_join_session_cbs_
.erase(it
);
362 void PresentationServiceImpl::SetDefaultPresentationURL(
363 const mojo::String
& url
) {
364 DVLOG(2) << "SetDefaultPresentationURL";
368 const std::string
& new_default_url
= url
.get();
369 if (default_presentation_url_
== new_default_url
)
371 delegate_
->SetDefaultPresentationUrl(
375 default_presentation_url_
= new_default_url
;
378 void PresentationServiceImpl::SendSessionMessage(
379 presentation::PresentationSessionInfoPtr session
,
380 presentation::SessionMessagePtr session_message
,
381 const SendMessageMojoCallback
& callback
) {
382 DVLOG(2) << "SendSessionMessage";
383 DCHECK(!session_message
.is_null());
384 // send_message_callback_ should be null by now, otherwise resetting of
385 // send_message_callback_ with new callback will drop the old callback.
386 if (!delegate_
|| send_message_callback_
) {
391 send_message_callback_
.reset(new SendMessageMojoCallback(callback
));
392 delegate_
->SendMessage(
393 render_process_id_
, render_frame_id_
,
394 session
.To
<PresentationSessionInfo
>(),
395 GetPresentationSessionMessage(session_message
.Pass()),
396 base::Bind(&PresentationServiceImpl::OnSendMessageCallback
,
397 weak_factory_
.GetWeakPtr()));
400 void PresentationServiceImpl::OnSendMessageCallback(bool sent
) {
401 // It is possible that Reset() is invoked before receiving this callback.
402 // So, always check send_message_callback_ for non-null.
403 if (send_message_callback_
) {
404 send_message_callback_
->Run(sent
);
405 send_message_callback_
.reset();
409 void PresentationServiceImpl::CloseSession(
410 const mojo::String
& presentation_url
,
411 const mojo::String
& presentation_id
) {
412 DVLOG(2) << "CloseSession " << presentation_id
;
414 delegate_
->CloseSession(render_process_id_
, render_frame_id_
,
418 void PresentationServiceImpl::ListenForSessionStateChange() {
422 delegate_
->ListenForSessionStateChange(
423 render_process_id_
, render_frame_id_
,
424 base::Bind(&PresentationServiceImpl::OnSessionStateChanged
,
425 weak_factory_
.GetWeakPtr()));
428 void PresentationServiceImpl::OnSessionStateChanged(
429 const PresentationSessionInfo
& session_info
,
430 PresentationSessionState session_state
) {
431 DCHECK(client_
.get());
432 client_
->OnSessionStateChanged(
433 presentation::PresentationSessionInfo::From(session_info
),
434 PresentationSessionStateToMojo(session_state
));
437 bool PresentationServiceImpl::FrameMatches(
438 content::RenderFrameHost
* render_frame_host
) const {
439 if (!render_frame_host
)
442 return render_frame_host
->GetProcess()->GetID() == render_process_id_
&&
443 render_frame_host
->GetRoutingID() == render_frame_id_
;
446 void PresentationServiceImpl::ListenForSessionMessages(
447 presentation::PresentationSessionInfoPtr session
) {
448 DVLOG(2) << "ListenForSessionMessages";
452 PresentationSessionInfo
session_info(session
.To
<PresentationSessionInfo
>());
453 delegate_
->ListenForSessionMessages(
454 render_process_id_
, render_frame_id_
, session_info
,
455 base::Bind(&PresentationServiceImpl::OnSessionMessages
,
456 weak_factory_
.GetWeakPtr(), session_info
));
459 void PresentationServiceImpl::OnSessionMessages(
460 const PresentationSessionInfo
& session
,
461 const ScopedVector
<PresentationSessionMessage
>& messages
,
462 bool pass_ownership
) {
465 DVLOG(2) << "OnSessionMessages";
466 mojo::Array
<presentation::SessionMessagePtr
> mojoMessages(messages
.size());
467 for (size_t i
= 0; i
< messages
.size(); ++i
)
468 mojoMessages
[i
] = ToMojoSessionMessage(messages
[i
], pass_ownership
);
470 client_
->OnSessionMessagesReceived(
471 presentation::PresentationSessionInfo::From(session
),
472 mojoMessages
.Pass());
475 void PresentationServiceImpl::DidNavigateAnyFrame(
476 content::RenderFrameHost
* render_frame_host
,
477 const content::LoadCommittedDetails
& details
,
478 const content::FrameNavigateParams
& params
) {
479 DVLOG(2) << "PresentationServiceImpl::DidNavigateAnyFrame";
480 if (!FrameMatches(render_frame_host
))
483 std::string prev_url_host
= details
.previous_url
.host();
484 std::string curr_url_host
= params
.url
.host();
486 // If a frame navigation is in-page (e.g. navigating to a fragment in
487 // same page) then we do not unregister listeners.
488 DVLOG(2) << "DidNavigateAnyFrame: "
489 << "prev host: " << prev_url_host
<< ", curr host: " << curr_url_host
490 << ", details.is_in_page: " << details
.is_in_page
;
491 if (details
.is_in_page
)
494 // Reset if the frame actually navigated.
498 void PresentationServiceImpl::RenderFrameDeleted(
499 content::RenderFrameHost
* render_frame_host
) {
500 DVLOG(2) << "PresentationServiceImpl::RenderFrameDeleted";
501 if (!FrameMatches(render_frame_host
))
504 // RenderFrameDeleted means the associated RFH is going to be deleted soon.
505 // This object should also be deleted.
510 void PresentationServiceImpl::Reset() {
511 DVLOG(2) << "PresentationServiceImpl::Reset";
513 delegate_
->Reset(render_process_id_
, render_frame_id_
);
515 default_presentation_url_
.clear();
517 screen_availability_listeners_
.clear();
519 start_session_request_id_
= kInvalidRequestSessionId
;
520 pending_start_session_cb_
.reset();
522 pending_join_session_cbs_
.clear();
524 default_session_start_context_
.reset();
526 if (on_session_messages_callback_
.get()) {
527 on_session_messages_callback_
->Run(
528 mojo::Array
<presentation::SessionMessagePtr
>());
529 on_session_messages_callback_
.reset();
532 if (send_message_callback_
) {
533 // Run the callback with false, indicating the renderer to stop sending
534 // the requests and invalidate all pending requests.
535 send_message_callback_
->Run(false);
536 send_message_callback_
.reset();
540 void PresentationServiceImpl::OnDelegateDestroyed() {
541 DVLOG(2) << "PresentationServiceImpl::OnDelegateDestroyed";
546 void PresentationServiceImpl::OnDefaultPresentationStarted(
547 const PresentationSessionInfo
& session
) {
548 if (default_session_start_context_
.get())
549 default_session_start_context_
->set_session(session
);
552 PresentationServiceImpl::ScreenAvailabilityListenerImpl
553 ::ScreenAvailabilityListenerImpl(
554 const std::string
& availability_url
,
555 PresentationServiceImpl
* service
)
556 : availability_url_(availability_url
),
559 DCHECK(service_
->client_
.get());
562 PresentationServiceImpl::ScreenAvailabilityListenerImpl::
563 ~ScreenAvailabilityListenerImpl() {
566 std::string
PresentationServiceImpl::ScreenAvailabilityListenerImpl
567 ::GetAvailabilityUrl() const {
568 return availability_url_
;
571 void PresentationServiceImpl::ScreenAvailabilityListenerImpl
572 ::OnScreenAvailabilityChanged(bool available
) {
573 service_
->client_
->OnScreenAvailabilityUpdated(availability_url_
, available
);
576 PresentationServiceImpl::NewSessionMojoCallbackWrapper
577 ::NewSessionMojoCallbackWrapper(const NewSessionMojoCallback
& callback
)
578 : callback_(callback
) {
581 PresentationServiceImpl::NewSessionMojoCallbackWrapper
582 ::~NewSessionMojoCallbackWrapper() {
583 if (!callback_
.is_null())
584 InvokeNewSessionMojoCallbackWithError(callback_
);
587 void PresentationServiceImpl::NewSessionMojoCallbackWrapper::Run(
588 presentation::PresentationSessionInfoPtr session
,
589 presentation::PresentationErrorPtr error
) {
590 DCHECK(!callback_
.is_null());
591 callback_
.Run(session
.Pass(), error
.Pass());
595 PresentationServiceImpl::DefaultSessionStartContext
596 ::DefaultSessionStartContext() {
599 PresentationServiceImpl::DefaultSessionStartContext
600 ::~DefaultSessionStartContext() {
604 void PresentationServiceImpl::DefaultSessionStartContext::AddCallback(
605 const DefaultSessionMojoCallback
& callback
) {
606 if (session_
.get()) {
607 DCHECK(callbacks_
.empty());
608 callback
.Run(presentation::PresentationSessionInfo::From(*session_
));
611 callbacks_
.push_back(new DefaultSessionMojoCallback(callback
));
615 void PresentationServiceImpl::DefaultSessionStartContext::set_session(
616 const PresentationSessionInfo
& session
) {
617 if (callbacks_
.empty()) {
618 session_
.reset(new PresentationSessionInfo(session
));
620 DCHECK(!session_
.get());
621 ScopedVector
<DefaultSessionMojoCallback
> callbacks
;
622 callbacks
.swap(callbacks_
);
623 for (const auto& callback
: callbacks
)
624 callback
->Run(presentation::PresentationSessionInfo::From(session
));
628 void PresentationServiceImpl::DefaultSessionStartContext::Reset() {
629 ScopedVector
<DefaultSessionMojoCallback
> callbacks
;
630 callbacks
.swap(callbacks_
);
631 for (const auto& callback
: callbacks
)
632 callback
->Run(presentation::PresentationSessionInfoPtr());
636 } // namespace content