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/modules/presentation/WebPresentationAvailabilityObserver.h"
18 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationController.h"
19 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationError.h"
20 #include "third_party/WebKit/public/web/WebLocalFrame.h"
25 blink::WebPresentationError::ErrorType
GetWebPresentationErrorTypeFromMojo(
26 presentation::PresentationErrorType mojoErrorType
) {
27 switch (mojoErrorType
) {
28 case presentation::PRESENTATION_ERROR_TYPE_NO_AVAILABLE_SCREENS
:
29 return blink::WebPresentationError::ErrorTypeNoAvailableScreens
;
30 case presentation::PRESENTATION_ERROR_TYPE_SESSION_REQUEST_CANCELLED
:
31 return blink::WebPresentationError::ErrorTypeSessionRequestCancelled
;
32 case presentation::PRESENTATION_ERROR_TYPE_NO_PRESENTATION_FOUND
:
33 return blink::WebPresentationError::ErrorTypeNoPresentationFound
;
34 case presentation::PRESENTATION_ERROR_TYPE_UNKNOWN
:
36 return blink::WebPresentationError::ErrorTypeUnknown
;
40 blink::WebPresentationSessionState
GetWebPresentationSessionStateFromMojo(
41 presentation::PresentationSessionState mojoSessionState
) {
42 switch (mojoSessionState
) {
43 case presentation::PRESENTATION_SESSION_STATE_CONNECTED
:
44 return blink::WebPresentationSessionState::Connected
;
45 case presentation::PRESENTATION_SESSION_STATE_DISCONNECTED
:
46 return blink::WebPresentationSessionState::Disconnected
;
50 return blink::WebPresentationSessionState::Disconnected
;
53 presentation::SessionMessage
* GetMojoSessionMessage(
54 const blink::WebString
& presentationUrl
,
55 const blink::WebString
& presentationId
,
56 presentation::PresentationMessageType type
,
59 presentation::SessionMessage
* session_message
=
60 new presentation::SessionMessage();
61 session_message
->presentation_url
= presentationUrl
.utf8();
62 session_message
->presentation_id
= presentationId
.utf8();
63 session_message
->type
= type
;
64 const std::vector
<uint8
> vector(data
, data
+ length
);
65 session_message
->data
= mojo::Array
<uint8
>::From(vector
);
66 return session_message
;
73 PresentationDispatcher::PresentationDispatcher(RenderFrame
* render_frame
)
74 : RenderFrameObserver(render_frame
),
77 listening_state_(ListeningState::Inactive
),
78 last_known_availability_(false),
79 listening_for_messages_(false) {
82 PresentationDispatcher::~PresentationDispatcher() {
83 // Controller should be destroyed before the dispatcher when frame is
88 void PresentationDispatcher::setController(
89 blink::WebPresentationController
* controller
) {
90 // There shouldn't be any swapping from one non-null controller to another.
91 DCHECK(controller
!= controller_
&& (!controller
|| !controller_
));
92 controller_
= controller
;
93 // The controller is set to null when the frame is about to be detached.
94 // Nothing is listening for screen availability anymore but the Mojo service
95 // will know about the frame being detached anyway.
98 void PresentationDispatcher::updateAvailableChangeWatched(bool watched
) {
99 ConnectToPresentationServiceIfNeeded();
101 presentation_service_
->ListenForScreenAvailability();
103 presentation_service_
->StopListeningForScreenAvailability();
106 void PresentationDispatcher::startSession(
107 const blink::WebString
& presentationUrl
,
108 blink::WebPresentationSessionClientCallbacks
* callback
) {
110 ConnectToPresentationServiceIfNeeded();
112 // The dispatcher owns the service so |this| will be valid when
113 // OnSessionCreated() is called. |callback| needs to be alive and also needs
114 // to be destroyed so we transfer its ownership to the mojo callback.
115 presentation_service_
->StartSession(
116 presentationUrl
.utf8(),
117 base::Bind(&PresentationDispatcher::OnSessionCreated
,
118 base::Unretained(this),
119 base::Owned(callback
)));
122 void PresentationDispatcher::joinSession(
123 const blink::WebString
& presentationUrl
,
124 const blink::WebString
& presentationId
,
125 blink::WebPresentationSessionClientCallbacks
* callback
) {
127 ConnectToPresentationServiceIfNeeded();
129 // The dispatcher owns the service so |this| will be valid when
130 // OnSessionCreated() is called. |callback| needs to be alive and also needs
131 // to be destroyed so we transfer its ownership to the mojo callback.
132 presentation_service_
->JoinSession(
133 presentationUrl
.utf8(),
134 presentationId
.utf8(),
135 base::Bind(&PresentationDispatcher::OnSessionCreated
,
136 base::Unretained(this),
137 base::Owned(callback
)));
140 void PresentationDispatcher::sendString(
141 const blink::WebString
& presentationUrl
,
142 const blink::WebString
& presentationId
,
143 const blink::WebString
& message
) {
144 if (message
.utf8().size() > kMaxPresentationSessionMessageSize
) {
145 // TODO(crbug.com/459008): Limit the size of individual messages to 64k
146 // for now. Consider throwing DOMException or splitting bigger messages
147 // into smaller chunks later.
148 LOG(WARNING
) << "message size exceeded limit!";
152 presentation::SessionMessage
* session_message
=
153 new presentation::SessionMessage();
154 session_message
->presentation_url
= presentationUrl
.utf8();
155 session_message
->presentation_id
= presentationId
.utf8();
156 session_message
->type
= presentation::PresentationMessageType::
157 PRESENTATION_MESSAGE_TYPE_TEXT
;
158 session_message
->message
= message
.utf8();
160 message_request_queue_
.push(make_linked_ptr(session_message
));
161 // Start processing request if only one in the queue.
162 if (message_request_queue_
.size() == 1) {
163 const linked_ptr
<presentation::SessionMessage
>& request
=
164 message_request_queue_
.front();
165 DoSendMessage(*request
);
169 void PresentationDispatcher::sendArrayBuffer(
170 const blink::WebString
& presentationUrl
,
171 const blink::WebString
& presentationId
,
175 if (length
> kMaxPresentationSessionMessageSize
) {
176 // TODO(crbug.com/459008): Same as in sendString().
177 LOG(WARNING
) << "data size exceeded limit!";
181 presentation::SessionMessage
* session_message
=
182 GetMojoSessionMessage(presentationUrl
, presentationId
,
183 presentation::PresentationMessageType::
184 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
,
186 message_request_queue_
.push(make_linked_ptr(session_message
));
187 // Start processing request if only one in the queue.
188 if (message_request_queue_
.size() == 1) {
189 const linked_ptr
<presentation::SessionMessage
>& request
=
190 message_request_queue_
.front();
191 DoSendMessage(*request
);
195 void PresentationDispatcher::sendBlobData(
196 const blink::WebString
& presentationUrl
,
197 const blink::WebString
& presentationId
,
201 if (length
> kMaxPresentationSessionMessageSize
) {
202 // TODO(crbug.com/459008): Same as in sendString().
203 LOG(WARNING
) << "data size exceeded limit!";
207 presentation::SessionMessage
* session_message
= GetMojoSessionMessage(
208 presentationUrl
, presentationId
,
209 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_BLOB
,
211 message_request_queue_
.push(make_linked_ptr(session_message
));
212 if (message_request_queue_
.size() == 1) {
213 const linked_ptr
<presentation::SessionMessage
>& request
=
214 message_request_queue_
.front();
215 DoSendMessage(*request
);
219 void PresentationDispatcher::DoSendMessage(
220 const presentation::SessionMessage
& session_message
) {
221 ConnectToPresentationServiceIfNeeded();
222 presentation::SessionMessagePtr
message_request(
223 presentation::SessionMessage::New());
224 message_request
->presentation_url
= session_message
.presentation_url
;
225 message_request
->presentation_id
= session_message
.presentation_id
;
226 message_request
->type
= session_message
.type
;
227 switch (session_message
.type
) {
228 case presentation::PresentationMessageType::
229 PRESENTATION_MESSAGE_TYPE_TEXT
: {
230 message_request
->message
= session_message
.message
;
233 case presentation::PresentationMessageType::
234 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
:
235 case presentation::PresentationMessageType::
236 PRESENTATION_MESSAGE_TYPE_BLOB
: {
237 message_request
->data
=
238 mojo::Array
<uint8
>::From(session_message
.data
.storage());
242 NOTREACHED() << "Invalid presentation message type "
243 << session_message
.type
;
248 presentation_service_
->SendSessionMessage(
249 message_request
.Pass(),
250 base::Bind(&PresentationDispatcher::HandleSendMessageRequests
,
251 base::Unretained(this)));
254 void PresentationDispatcher::HandleSendMessageRequests(bool success
) {
255 // In normal cases, message_request_queue_ should not be empty at this point
256 // of time, but when DidCommitProvisionalLoad() is invoked before receiving
257 // the callback for previous send mojo call, queue would have been emptied.
258 if (message_request_queue_
.empty())
262 // PresentationServiceImpl is informing that Frame has been detached or
263 // navigated away. Invalidate all pending requests.
264 MessageRequestQueue empty
;
265 std::swap(message_request_queue_
, empty
);
269 message_request_queue_
.pop();
270 if (!message_request_queue_
.empty()) {
271 const linked_ptr
<presentation::SessionMessage
>& request
=
272 message_request_queue_
.front();
273 DoSendMessage(*request
);
277 void PresentationDispatcher::closeSession(
278 const blink::WebString
& presentationUrl
,
279 const blink::WebString
& presentationId
) {
280 ConnectToPresentationServiceIfNeeded();
282 presentation_service_
->CloseSession(
283 presentationUrl
.utf8(),
284 presentationId
.utf8());
287 void PresentationDispatcher::getAvailability(
288 const blink::WebString
& presentationUrl
,
289 blink::WebPresentationAvailabilityCallbacks
* callbacks
) {
290 if (listening_state_
== ListeningState::Active
) {
291 callbacks
->onSuccess(&last_known_availability_
);
296 availability_callbacks_
.Add(callbacks
);
297 UpdateListeningState();
300 void PresentationDispatcher::startListening(
301 blink::WebPresentationAvailabilityObserver
* observer
) {
302 availability_observers_
.insert(observer
);
303 UpdateListeningState();
306 void PresentationDispatcher::stopListening(
307 blink::WebPresentationAvailabilityObserver
* observer
) {
308 availability_observers_
.erase(observer
);
309 UpdateListeningState();
312 void PresentationDispatcher::setDefaultPresentationUrl(
313 const blink::WebString
& url
)
315 ConnectToPresentationServiceIfNeeded();
316 presentation_service_
->SetDefaultPresentationURL(url
.utf8());
319 void PresentationDispatcher::DidCommitProvisionalLoad(
320 bool is_new_navigation
,
321 bool is_same_page_navigation
) {
322 blink::WebFrame
* frame
= render_frame()->GetWebFrame();
323 // If not top-level navigation.
324 if (frame
->parent() || is_same_page_navigation
)
327 // Remove all pending send message requests.
328 MessageRequestQueue empty
;
329 std::swap(message_request_queue_
, empty
);
331 listening_for_messages_
= false;
334 void PresentationDispatcher::OnScreenAvailabilityUpdated(bool available
) {
335 last_known_availability_
= available
;
337 if (listening_state_
== ListeningState::Waiting
)
338 listening_state_
= ListeningState::Active
;
340 for (auto observer
: availability_observers_
)
341 observer
->availabilityChanged(available
);
343 for (AvailabilityCallbacksMap::iterator
iter(&availability_callbacks_
);
344 !iter
.IsAtEnd(); iter
.Advance()) {
345 iter
.GetCurrentValue()->onSuccess(&available
);
347 availability_callbacks_
.Clear();
349 UpdateListeningState();
352 void PresentationDispatcher::OnScreenAvailabilityNotSupported() {
353 DCHECK(listening_state_
== ListeningState::Waiting
);
355 for (AvailabilityCallbacksMap::iterator
iter(&availability_callbacks_
);
356 !iter
.IsAtEnd(); iter
.Advance()) {
357 iter
.GetCurrentValue()->onError(new blink::WebPresentationError(
358 blink::WebPresentationError::ErrorTypeAvailabilityNotSupported
,
359 blink::WebString::fromUTF8(
360 "getAvailability() isn't supported at the moment. It can be due to"
361 "a permanent or temporary system limitation. It is recommended to"
362 "try to blindly start a session in that case.")));
364 availability_callbacks_
.Clear();
366 UpdateListeningState();
369 void PresentationDispatcher::OnDefaultSessionStarted(
370 presentation::PresentationSessionInfoPtr session_info
) {
374 // Reset the callback to get the next event.
375 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
376 &PresentationDispatcher::OnDefaultSessionStarted
,
377 base::Unretained(this)));
379 if (!session_info
.is_null()) {
380 controller_
->didStartDefaultSession(
381 new PresentationSessionClient(session_info
.Pass()));
382 StartListenForMessages();
386 void PresentationDispatcher::OnSessionCreated(
387 blink::WebPresentationSessionClientCallbacks
* callback
,
388 presentation::PresentationSessionInfoPtr session_info
,
389 presentation::PresentationErrorPtr error
) {
391 if (!error
.is_null()) {
392 DCHECK(session_info
.is_null());
393 callback
->onError(new blink::WebPresentationError(
394 GetWebPresentationErrorTypeFromMojo(error
->error_type
),
395 blink::WebString::fromUTF8(error
->message
)));
399 DCHECK(!session_info
.is_null());
400 callback
->onSuccess(new PresentationSessionClient(session_info
.Pass()));
401 StartListenForMessages();
404 void PresentationDispatcher::StartListenForMessages() {
405 if (listening_for_messages_
)
408 listening_for_messages_
= true;
409 presentation_service_
->ListenForSessionMessages(
410 base::Bind(&PresentationDispatcher::OnSessionMessagesReceived
,
411 base::Unretained(this)));
414 void PresentationDispatcher::OnSessionStateChanged(
415 presentation::PresentationSessionInfoPtr session_info
,
416 presentation::PresentationSessionState session_state
) {
420 DCHECK(!session_info
.is_null());
421 controller_
->didChangeSessionState(
422 new PresentationSessionClient(session_info
.Pass()),
423 GetWebPresentationSessionStateFromMojo(session_state
));
426 void PresentationDispatcher::OnSessionMessagesReceived(
427 mojo::Array
<presentation::SessionMessagePtr
> messages
) {
428 if (!listening_for_messages_
)
429 return; // messages may come after the frame navigated.
431 // When messages is null, there is an error at presentation service side.
432 if (!controller_
|| messages
.is_null()) {
433 listening_for_messages_
= false;
437 for (size_t i
= 0; i
< messages
.size(); ++i
) {
438 // Note: Passing batches of messages to the Blink layer would be more
440 scoped_ptr
<PresentationSessionClient
> session_client(
441 new PresentationSessionClient(messages
[i
]->presentation_url
,
442 messages
[i
]->presentation_id
));
443 switch (messages
[i
]->type
) {
444 case presentation::PresentationMessageType::
445 PRESENTATION_MESSAGE_TYPE_TEXT
: {
446 controller_
->didReceiveSessionTextMessage(
447 session_client
.release(),
448 blink::WebString::fromUTF8(messages
[i
]->message
));
451 case presentation::PresentationMessageType::
452 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
:
453 case presentation::PresentationMessageType::
454 PRESENTATION_MESSAGE_TYPE_BLOB
: {
455 controller_
->didReceiveSessionBinaryMessage(
456 session_client
.release(), &(messages
[i
]->data
.front()),
457 messages
[i
]->data
.size());
467 presentation_service_
->ListenForSessionMessages(
468 base::Bind(&PresentationDispatcher::OnSessionMessagesReceived
,
469 base::Unretained(this)));
472 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
473 if (presentation_service_
.get())
476 render_frame()->GetServiceRegistry()->ConnectToRemoteService(
477 mojo::GetProxy(&presentation_service_
));
478 presentation::PresentationServiceClientPtr client_ptr
;
479 binding_
.Bind(GetProxy(&client_ptr
));
480 presentation_service_
->SetClient(client_ptr
.Pass());
482 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
483 &PresentationDispatcher::OnDefaultSessionStarted
,
484 base::Unretained(this)));
485 presentation_service_
->ListenForSessionStateChange();
488 void PresentationDispatcher::UpdateListeningState() {
489 bool should_listen
= !availability_callbacks_
.IsEmpty() ||
490 !availability_observers_
.empty();
491 bool is_listening
= listening_state_
!= ListeningState::Inactive
;
493 if (should_listen
== is_listening
)
496 ConnectToPresentationServiceIfNeeded();
498 listening_state_
= ListeningState::Waiting
;
499 presentation_service_
->ListenForScreenAvailability();
501 listening_state_
= ListeningState::Inactive
;
502 presentation_service_
->StopListeningForScreenAvailability();
506 } // namespace content