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/renderer/presentation/presentation_dispatcher.h"
10 #include "base/logging.h"
11 #include "content/common/presentation/presentation_service.mojom.h"
12 #include "content/public/common/presentation_constants.h"
13 #include "content/public/common/service_registry.h"
14 #include "content/public/renderer/render_frame.h"
15 #include "content/renderer/presentation/presentation_session_client.h"
16 #include "third_party/WebKit/public/platform/WebString.h"
17 #include "third_party/WebKit/public/platform/WebURL.h"
18 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationAvailabilityObserver.h"
19 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationController.h"
20 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationError.h"
21 #include "third_party/WebKit/public/web/WebLocalFrame.h"
26 blink::WebPresentationError::ErrorType
GetWebPresentationErrorTypeFromMojo(
27 presentation::PresentationErrorType mojoErrorType
) {
28 switch (mojoErrorType
) {
29 case presentation::PRESENTATION_ERROR_TYPE_NO_AVAILABLE_SCREENS
:
30 return blink::WebPresentationError::ErrorTypeNoAvailableScreens
;
31 case presentation::PRESENTATION_ERROR_TYPE_SESSION_REQUEST_CANCELLED
:
32 return blink::WebPresentationError::ErrorTypeSessionRequestCancelled
;
33 case presentation::PRESENTATION_ERROR_TYPE_NO_PRESENTATION_FOUND
:
34 return blink::WebPresentationError::ErrorTypeNoPresentationFound
;
35 case presentation::PRESENTATION_ERROR_TYPE_UNKNOWN
:
37 return blink::WebPresentationError::ErrorTypeUnknown
;
41 blink::WebPresentationSessionState
GetWebPresentationSessionStateFromMojo(
42 presentation::PresentationSessionState mojoSessionState
) {
43 switch (mojoSessionState
) {
44 case presentation::PRESENTATION_SESSION_STATE_CONNECTED
:
45 return blink::WebPresentationSessionState::Connected
;
46 case presentation::PRESENTATION_SESSION_STATE_DISCONNECTED
:
47 return blink::WebPresentationSessionState::Disconnected
;
51 return blink::WebPresentationSessionState::Disconnected
;
58 PresentationDispatcher::PresentationDispatcher(RenderFrame
* render_frame
)
59 : RenderFrameObserver(render_frame
),
64 PresentationDispatcher::~PresentationDispatcher() {
65 // Controller should be destroyed before the dispatcher when frame is
70 void PresentationDispatcher::setController(
71 blink::WebPresentationController
* controller
) {
72 // There shouldn't be any swapping from one non-null controller to another.
73 DCHECK(controller
!= controller_
&& (!controller
|| !controller_
));
74 controller_
= controller
;
75 // The controller is set to null when the frame is about to be detached.
76 // Nothing is listening for screen availability anymore but the Mojo service
77 // will know about the frame being detached anyway.
80 void PresentationDispatcher::startSession(
81 const blink::WebString
& presentationUrl
,
82 blink::WebPresentationSessionClientCallbacks
* callback
) {
84 ConnectToPresentationServiceIfNeeded();
86 // The dispatcher owns the service so |this| will be valid when
87 // OnSessionCreated() is called. |callback| needs to be alive and also needs
88 // to be destroyed so we transfer its ownership to the mojo callback.
89 presentation_service_
->StartSession(
90 presentationUrl
.utf8(),
91 base::Bind(&PresentationDispatcher::OnSessionCreated
,
92 base::Unretained(this),
93 base::Owned(callback
)));
96 void PresentationDispatcher::joinSession(
97 const blink::WebString
& presentationUrl
,
98 const blink::WebString
& presentationId
,
99 blink::WebPresentationSessionClientCallbacks
* callback
) {
101 ConnectToPresentationServiceIfNeeded();
103 // The dispatcher owns the service so |this| will be valid when
104 // OnSessionCreated() is called. |callback| needs to be alive and also needs
105 // to be destroyed so we transfer its ownership to the mojo callback.
106 presentation_service_
->JoinSession(
107 presentationUrl
.utf8(),
108 presentationId
.utf8(),
109 base::Bind(&PresentationDispatcher::OnSessionCreated
,
110 base::Unretained(this),
111 base::Owned(callback
)));
114 void PresentationDispatcher::sendString(
115 const blink::WebString
& presentationUrl
,
116 const blink::WebString
& presentationId
,
117 const blink::WebString
& message
) {
118 if (message
.utf8().size() > kMaxPresentationSessionMessageSize
) {
119 // TODO(crbug.com/459008): Limit the size of individual messages to 64k
120 // for now. Consider throwing DOMException or splitting bigger messages
121 // into smaller chunks later.
122 LOG(WARNING
) << "message size exceeded limit!";
126 message_request_queue_
.push(make_linked_ptr(
127 CreateSendTextMessageRequest(presentationUrl
, presentationId
, message
)));
128 // Start processing request if only one in the queue.
129 if (message_request_queue_
.size() == 1)
130 DoSendMessage(message_request_queue_
.front().get());
133 void PresentationDispatcher::sendArrayBuffer(
134 const blink::WebString
& presentationUrl
,
135 const blink::WebString
& presentationId
,
139 if (length
> kMaxPresentationSessionMessageSize
) {
140 // TODO(crbug.com/459008): Same as in sendString().
141 LOG(WARNING
) << "data size exceeded limit!";
145 message_request_queue_
.push(make_linked_ptr(
146 CreateSendBinaryMessageRequest(presentationUrl
, presentationId
,
147 presentation::PresentationMessageType::
148 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
,
150 // Start processing request if only one in the queue.
151 if (message_request_queue_
.size() == 1)
152 DoSendMessage(message_request_queue_
.front().get());
155 void PresentationDispatcher::sendBlobData(
156 const blink::WebString
& presentationUrl
,
157 const blink::WebString
& presentationId
,
161 if (length
> kMaxPresentationSessionMessageSize
) {
162 // TODO(crbug.com/459008): Same as in sendString().
163 LOG(WARNING
) << "data size exceeded limit!";
167 message_request_queue_
.push(make_linked_ptr(CreateSendBinaryMessageRequest(
168 presentationUrl
, presentationId
,
169 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_BLOB
,
171 // Start processing request if only one in the queue.
172 if (message_request_queue_
.size() == 1)
173 DoSendMessage(message_request_queue_
.front().get());
176 void PresentationDispatcher::DoSendMessage(SendMessageRequest
* request
) {
177 ConnectToPresentationServiceIfNeeded();
179 presentation_service_
->SendSessionMessage(
180 request
->session_info
.Pass(), request
->message
.Pass(),
181 base::Bind(&PresentationDispatcher::HandleSendMessageRequests
,
182 base::Unretained(this)));
185 void PresentationDispatcher::HandleSendMessageRequests(bool success
) {
186 // In normal cases, message_request_queue_ should not be empty at this point
187 // of time, but when DidCommitProvisionalLoad() is invoked before receiving
188 // the callback for previous send mojo call, queue would have been emptied.
189 if (message_request_queue_
.empty())
193 // PresentationServiceImpl is informing that Frame has been detached or
194 // navigated away. Invalidate all pending requests.
195 MessageRequestQueue empty
;
196 std::swap(message_request_queue_
, empty
);
200 message_request_queue_
.pop();
201 if (!message_request_queue_
.empty()) {
202 DoSendMessage(message_request_queue_
.front().get());
206 void PresentationDispatcher::closeSession(
207 const blink::WebString
& presentationUrl
,
208 const blink::WebString
& presentationId
) {
209 ConnectToPresentationServiceIfNeeded();
211 presentation_service_
->CloseSession(
212 presentationUrl
.utf8(),
213 presentationId
.utf8());
216 void PresentationDispatcher::getAvailability(
217 const blink::WebString
& availabilityUrl
,
218 blink::WebPresentationAvailabilityCallbacks
* callbacks
) {
219 const std::string
& availability_url
= availabilityUrl
.utf8();
220 AvailabilityStatus
* status
= nullptr;
221 auto status_it
= availability_status_
.find(availability_url
);
222 if (status_it
== availability_status_
.end()) {
223 status
= new AvailabilityStatus(availability_url
);
224 availability_status_
.set(availability_url
, make_scoped_ptr(status
));
226 status
= status_it
->second
;
230 if (status
->listening_state
== ListeningState::ACTIVE
) {
231 callbacks
->onSuccess(status
->last_known_availability
);
236 status
->availability_callbacks
.Add(callbacks
);
237 UpdateListeningState(status
);
240 void PresentationDispatcher::startListening(
241 blink::WebPresentationAvailabilityObserver
* observer
) {
242 const std::string
& availability_url
= observer
->url().string().utf8();
243 auto status_it
= availability_status_
.find(availability_url
);
244 if (status_it
== availability_status_
.end()) {
245 DLOG(WARNING
) << "Start listening for availability for unknown URL "
249 status_it
->second
->availability_observers
.insert(observer
);
250 UpdateListeningState(status_it
->second
);
253 void PresentationDispatcher::stopListening(
254 blink::WebPresentationAvailabilityObserver
* observer
) {
255 const std::string
& availability_url
= observer
->url().string().utf8();
256 auto status_it
= availability_status_
.find(availability_url
);
257 if (status_it
== availability_status_
.end()) {
258 DLOG(WARNING
) << "Stop listening for availability for unknown URL "
262 status_it
->second
->availability_observers
.erase(observer
);
263 UpdateListeningState(status_it
->second
);
266 void PresentationDispatcher::setDefaultPresentationUrl(
267 const blink::WebString
& url
) {
268 ConnectToPresentationServiceIfNeeded();
269 presentation_service_
->SetDefaultPresentationURL(url
.utf8());
272 void PresentationDispatcher::DidCommitProvisionalLoad(
273 bool is_new_navigation
,
274 bool is_same_page_navigation
) {
275 blink::WebFrame
* frame
= render_frame()->GetWebFrame();
276 // If not top-level navigation.
277 if (frame
->parent() || is_same_page_navigation
)
280 // Remove all pending send message requests.
281 MessageRequestQueue empty
;
282 std::swap(message_request_queue_
, empty
);
285 void PresentationDispatcher::OnScreenAvailabilityUpdated(
286 const mojo::String
& url
, bool available
) {
287 const std::string
& availability_url
= url
.get();
288 auto status_it
= availability_status_
.find(availability_url
);
289 if (status_it
== availability_status_
.end())
291 AvailabilityStatus
* status
= status_it
->second
;
294 if (status
->listening_state
== ListeningState::WAITING
)
295 status
->listening_state
= ListeningState::ACTIVE
;
297 for (auto observer
: status
->availability_observers
)
298 observer
->availabilityChanged(available
);
300 for (AvailabilityCallbacksMap::iterator
iter(&status
->availability_callbacks
);
301 !iter
.IsAtEnd(); iter
.Advance()) {
302 iter
.GetCurrentValue()->onSuccess(available
);
304 status
->last_known_availability
= available
;
305 status
->availability_callbacks
.Clear();
306 UpdateListeningState(status
);
309 void PresentationDispatcher::OnScreenAvailabilityNotSupported(
310 const mojo::String
& url
) {
311 const std::string
& availability_url
= url
.get();
312 auto status_it
= availability_status_
.find(availability_url
);
313 if (status_it
== availability_status_
.end())
315 AvailabilityStatus
* status
= status_it
->second
;
317 DCHECK(status
->listening_state
== ListeningState::WAITING
);
319 const blink::WebString
& not_supported_error
= blink::WebString::fromUTF8(
320 "getAvailability() isn't supported at the moment. It can be due to "
321 "a permanent or temporary system limitation. It is recommended to "
322 "try to blindly start a session in that case.");
323 for (AvailabilityCallbacksMap::iterator
iter(&status
->availability_callbacks
);
324 !iter
.IsAtEnd(); iter
.Advance()) {
325 iter
.GetCurrentValue()->onError(blink::WebPresentationError(
326 blink::WebPresentationError::ErrorTypeAvailabilityNotSupported
,
327 not_supported_error
));
329 status
->availability_callbacks
.Clear();
330 UpdateListeningState(status
);
333 void PresentationDispatcher::OnDefaultSessionStarted(
334 presentation::PresentationSessionInfoPtr session_info
) {
338 // Reset the callback to get the next event.
339 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
340 &PresentationDispatcher::OnDefaultSessionStarted
,
341 base::Unretained(this)));
343 if (!session_info
.is_null()) {
344 controller_
->didStartDefaultSession(
345 new PresentationSessionClient(session_info
.Clone()));
346 presentation_service_
->ListenForSessionMessages(session_info
.Pass());
350 void PresentationDispatcher::OnSessionCreated(
351 blink::WebPresentationSessionClientCallbacks
* callback
,
352 presentation::PresentationSessionInfoPtr session_info
,
353 presentation::PresentationErrorPtr error
) {
355 if (!error
.is_null()) {
356 DCHECK(session_info
.is_null());
357 callback
->onError(blink::WebPresentationError(
358 GetWebPresentationErrorTypeFromMojo(error
->error_type
),
359 blink::WebString::fromUTF8(error
->message
)));
363 DCHECK(!session_info
.is_null());
365 blink::adoptWebPtr(new PresentationSessionClient(session_info
.Clone())));
366 presentation_service_
->ListenForSessionMessages(session_info
.Pass());
369 void PresentationDispatcher::OnSessionStateChanged(
370 presentation::PresentationSessionInfoPtr session_info
,
371 presentation::PresentationSessionState session_state
) {
375 DCHECK(!session_info
.is_null());
376 controller_
->didChangeSessionState(
377 new PresentationSessionClient(session_info
.Pass()),
378 GetWebPresentationSessionStateFromMojo(session_state
));
381 void PresentationDispatcher::OnSessionMessagesReceived(
382 presentation::PresentationSessionInfoPtr session_info
,
383 mojo::Array
<presentation::SessionMessagePtr
> messages
) {
387 for (size_t i
= 0; i
< messages
.size(); ++i
) {
388 // Note: Passing batches of messages to the Blink layer would be more
390 scoped_ptr
<PresentationSessionClient
> session_client(
391 new PresentationSessionClient(session_info
->url
, session_info
->id
));
392 switch (messages
[i
]->type
) {
393 case presentation::PresentationMessageType::
394 PRESENTATION_MESSAGE_TYPE_TEXT
: {
395 controller_
->didReceiveSessionTextMessage(
396 session_client
.release(),
397 blink::WebString::fromUTF8(messages
[i
]->message
));
400 case presentation::PresentationMessageType::
401 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
:
402 case presentation::PresentationMessageType::
403 PRESENTATION_MESSAGE_TYPE_BLOB
: {
404 controller_
->didReceiveSessionBinaryMessage(
405 session_client
.release(), &(messages
[i
]->data
.front()),
406 messages
[i
]->data
.size());
417 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
418 if (presentation_service_
.get())
421 render_frame()->GetServiceRegistry()->ConnectToRemoteService(
422 mojo::GetProxy(&presentation_service_
));
423 presentation::PresentationServiceClientPtr client_ptr
;
424 binding_
.Bind(GetProxy(&client_ptr
));
425 presentation_service_
->SetClient(client_ptr
.Pass());
427 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
428 &PresentationDispatcher::OnDefaultSessionStarted
,
429 base::Unretained(this)));
430 presentation_service_
->ListenForSessionStateChange();
433 void PresentationDispatcher::UpdateListeningState(AvailabilityStatus
* status
) {
434 bool should_listen
= !status
->availability_callbacks
.IsEmpty() ||
435 !status
->availability_observers
.empty();
436 bool is_listening
= status
->listening_state
!= ListeningState::INACTIVE
;
438 if (should_listen
== is_listening
)
441 ConnectToPresentationServiceIfNeeded();
443 status
->listening_state
= ListeningState::WAITING
;
444 presentation_service_
->ListenForScreenAvailability(status
->url
);
446 status
->listening_state
= ListeningState::INACTIVE
;
447 presentation_service_
->StopListeningForScreenAvailability(status
->url
);
451 PresentationDispatcher::SendMessageRequest::SendMessageRequest(
452 presentation::PresentationSessionInfoPtr session_info
,
453 presentation::SessionMessagePtr message
)
454 : session_info(session_info
.Pass()), message(message
.Pass()) {}
456 PresentationDispatcher::SendMessageRequest::~SendMessageRequest() {}
459 PresentationDispatcher::SendMessageRequest
*
460 PresentationDispatcher::CreateSendTextMessageRequest(
461 const blink::WebString
& presentationUrl
,
462 const blink::WebString
& presentationId
,
463 const blink::WebString
& message
) {
464 presentation::PresentationSessionInfoPtr session_info
=
465 presentation::PresentationSessionInfo::New();
466 session_info
->url
= presentationUrl
.utf8();
467 session_info
->id
= presentationId
.utf8();
469 presentation::SessionMessagePtr session_message
=
470 presentation::SessionMessage::New();
471 session_message
->type
=
472 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT
;
473 session_message
->message
= message
.utf8();
474 return new SendMessageRequest(session_info
.Pass(), session_message
.Pass());
478 PresentationDispatcher::SendMessageRequest
*
479 PresentationDispatcher::CreateSendBinaryMessageRequest(
480 const blink::WebString
& presentationUrl
,
481 const blink::WebString
& presentationId
,
482 presentation::PresentationMessageType type
,
485 presentation::PresentationSessionInfoPtr session_info
=
486 presentation::PresentationSessionInfo::New();
487 session_info
->url
= presentationUrl
.utf8();
488 session_info
->id
= presentationId
.utf8();
490 presentation::SessionMessagePtr session_message
=
491 presentation::SessionMessage::New();
492 session_message
->type
= type
;
493 std::vector
<uint8
> tmp_data_vector(data
, data
+ length
);
494 session_message
->data
.Swap(&tmp_data_vector
);
495 return new SendMessageRequest(session_info
.Pass(), session_message
.Pass());
498 PresentationDispatcher::AvailabilityStatus::AvailabilityStatus(
499 const std::string
& availability_url
)
500 : url(availability_url
),
501 last_known_availability(false),
502 listening_state(ListeningState::INACTIVE
) {}
504 PresentationDispatcher::AvailabilityStatus::~AvailabilityStatus() {
507 } // namespace content