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
;
57 PresentationDispatcher::PresentationDispatcher(RenderFrame
* render_frame
)
58 : RenderFrameObserver(render_frame
),
61 listening_state_(ListeningState::Inactive
),
62 last_known_availability_(false) {}
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::updateAvailableChangeWatched(bool watched
) {
81 ConnectToPresentationServiceIfNeeded();
83 presentation_service_
->ListenForScreenAvailability();
85 presentation_service_
->StopListeningForScreenAvailability();
88 void PresentationDispatcher::startSession(
89 const blink::WebString
& presentationUrl
,
90 blink::WebPresentationSessionClientCallbacks
* callback
) {
92 ConnectToPresentationServiceIfNeeded();
94 // The dispatcher owns the service so |this| will be valid when
95 // OnSessionCreated() is called. |callback| needs to be alive and also needs
96 // to be destroyed so we transfer its ownership to the mojo callback.
97 presentation_service_
->StartSession(
98 presentationUrl
.utf8(),
99 base::Bind(&PresentationDispatcher::OnSessionCreated
,
100 base::Unretained(this),
101 base::Owned(callback
)));
104 void PresentationDispatcher::joinSession(
105 const blink::WebString
& presentationUrl
,
106 const blink::WebString
& presentationId
,
107 blink::WebPresentationSessionClientCallbacks
* callback
) {
109 ConnectToPresentationServiceIfNeeded();
111 // The dispatcher owns the service so |this| will be valid when
112 // OnSessionCreated() is called. |callback| needs to be alive and also needs
113 // to be destroyed so we transfer its ownership to the mojo callback.
114 presentation_service_
->JoinSession(
115 presentationUrl
.utf8(),
116 presentationId
.utf8(),
117 base::Bind(&PresentationDispatcher::OnSessionCreated
,
118 base::Unretained(this),
119 base::Owned(callback
)));
122 void PresentationDispatcher::sendString(
123 const blink::WebString
& presentationUrl
,
124 const blink::WebString
& presentationId
,
125 const blink::WebString
& message
) {
126 if (message
.utf8().size() > kMaxPresentationSessionMessageSize
) {
127 // TODO(crbug.com/459008): Limit the size of individual messages to 64k
128 // for now. Consider throwing DOMException or splitting bigger messages
129 // into smaller chunks later.
130 LOG(WARNING
) << "message size exceeded limit!";
134 message_request_queue_
.push(make_linked_ptr(
135 CreateSendTextMessageRequest(presentationUrl
, presentationId
, message
)));
136 // Start processing request if only one in the queue.
137 if (message_request_queue_
.size() == 1)
138 DoSendMessage(message_request_queue_
.front().get());
141 void PresentationDispatcher::sendArrayBuffer(
142 const blink::WebString
& presentationUrl
,
143 const blink::WebString
& presentationId
,
147 if (length
> kMaxPresentationSessionMessageSize
) {
148 // TODO(crbug.com/459008): Same as in sendString().
149 LOG(WARNING
) << "data size exceeded limit!";
153 message_request_queue_
.push(make_linked_ptr(
154 CreateSendBinaryMessageRequest(presentationUrl
, presentationId
,
155 presentation::PresentationMessageType::
156 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
,
158 // Start processing request if only one in the queue.
159 if (message_request_queue_
.size() == 1)
160 DoSendMessage(message_request_queue_
.front().get());
163 void PresentationDispatcher::sendBlobData(
164 const blink::WebString
& presentationUrl
,
165 const blink::WebString
& presentationId
,
169 if (length
> kMaxPresentationSessionMessageSize
) {
170 // TODO(crbug.com/459008): Same as in sendString().
171 LOG(WARNING
) << "data size exceeded limit!";
175 message_request_queue_
.push(make_linked_ptr(CreateSendBinaryMessageRequest(
176 presentationUrl
, presentationId
,
177 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_BLOB
,
179 // Start processing request if only one in the queue.
180 if (message_request_queue_
.size() == 1)
181 DoSendMessage(message_request_queue_
.front().get());
184 void PresentationDispatcher::DoSendMessage(SendMessageRequest
* request
) {
185 ConnectToPresentationServiceIfNeeded();
187 presentation_service_
->SendSessionMessage(
188 request
->session_info
.Pass(), request
->message
.Pass(),
189 base::Bind(&PresentationDispatcher::HandleSendMessageRequests
,
190 base::Unretained(this)));
193 void PresentationDispatcher::HandleSendMessageRequests(bool success
) {
194 // In normal cases, message_request_queue_ should not be empty at this point
195 // of time, but when DidCommitProvisionalLoad() is invoked before receiving
196 // the callback for previous send mojo call, queue would have been emptied.
197 if (message_request_queue_
.empty())
201 // PresentationServiceImpl is informing that Frame has been detached or
202 // navigated away. Invalidate all pending requests.
203 MessageRequestQueue empty
;
204 std::swap(message_request_queue_
, empty
);
208 message_request_queue_
.pop();
209 if (!message_request_queue_
.empty()) {
210 DoSendMessage(message_request_queue_
.front().get());
214 void PresentationDispatcher::closeSession(
215 const blink::WebString
& presentationUrl
,
216 const blink::WebString
& presentationId
) {
217 ConnectToPresentationServiceIfNeeded();
219 presentation_service_
->CloseSession(
220 presentationUrl
.utf8(),
221 presentationId
.utf8());
224 void PresentationDispatcher::getAvailability(
225 const blink::WebString
& presentationUrl
,
226 blink::WebPresentationAvailabilityCallbacks
* callbacks
) {
227 if (listening_state_
== ListeningState::Active
) {
228 callbacks
->onSuccess(new bool(last_known_availability_
));
233 availability_callbacks_
.Add(callbacks
);
234 UpdateListeningState();
237 void PresentationDispatcher::startListening(
238 blink::WebPresentationAvailabilityObserver
* observer
) {
239 availability_observers_
.insert(observer
);
240 UpdateListeningState();
243 void PresentationDispatcher::stopListening(
244 blink::WebPresentationAvailabilityObserver
* observer
) {
245 availability_observers_
.erase(observer
);
246 UpdateListeningState();
249 void PresentationDispatcher::setDefaultPresentationUrl(
250 const blink::WebString
& url
)
252 ConnectToPresentationServiceIfNeeded();
253 presentation_service_
->SetDefaultPresentationURL(url
.utf8());
256 void PresentationDispatcher::DidCommitProvisionalLoad(
257 bool is_new_navigation
,
258 bool is_same_page_navigation
) {
259 blink::WebFrame
* frame
= render_frame()->GetWebFrame();
260 // If not top-level navigation.
261 if (frame
->parent() || is_same_page_navigation
)
264 // Remove all pending send message requests.
265 MessageRequestQueue empty
;
266 std::swap(message_request_queue_
, empty
);
269 void PresentationDispatcher::OnScreenAvailabilityUpdated(bool available
) {
270 last_known_availability_
= available
;
272 if (listening_state_
== ListeningState::Waiting
)
273 listening_state_
= ListeningState::Active
;
275 for (auto observer
: availability_observers_
)
276 observer
->availabilityChanged(available
);
278 for (AvailabilityCallbacksMap::iterator
iter(&availability_callbacks_
);
279 !iter
.IsAtEnd(); iter
.Advance()) {
280 iter
.GetCurrentValue()->onSuccess(new bool(available
));
282 availability_callbacks_
.Clear();
284 UpdateListeningState();
287 void PresentationDispatcher::OnScreenAvailabilityNotSupported() {
288 DCHECK(listening_state_
== ListeningState::Waiting
);
290 for (AvailabilityCallbacksMap::iterator
iter(&availability_callbacks_
);
291 !iter
.IsAtEnd(); iter
.Advance()) {
292 iter
.GetCurrentValue()->onError(new blink::WebPresentationError(
293 blink::WebPresentationError::ErrorTypeAvailabilityNotSupported
,
294 blink::WebString::fromUTF8(
295 "getAvailability() isn't supported at the moment. It can be due to"
296 "a permanent or temporary system limitation. It is recommended to"
297 "try to blindly start a session in that case.")));
299 availability_callbacks_
.Clear();
301 UpdateListeningState();
304 void PresentationDispatcher::OnDefaultSessionStarted(
305 presentation::PresentationSessionInfoPtr session_info
) {
309 // Reset the callback to get the next event.
310 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
311 &PresentationDispatcher::OnDefaultSessionStarted
,
312 base::Unretained(this)));
314 if (!session_info
.is_null()) {
315 controller_
->didStartDefaultSession(
316 new PresentationSessionClient(session_info
.Clone()));
317 presentation_service_
->ListenForSessionMessages(session_info
.Pass());
321 void PresentationDispatcher::OnSessionCreated(
322 blink::WebPresentationSessionClientCallbacks
* callback
,
323 presentation::PresentationSessionInfoPtr session_info
,
324 presentation::PresentationErrorPtr error
) {
326 if (!error
.is_null()) {
327 DCHECK(session_info
.is_null());
328 callback
->onError(new blink::WebPresentationError(
329 GetWebPresentationErrorTypeFromMojo(error
->error_type
),
330 blink::WebString::fromUTF8(error
->message
)));
334 DCHECK(!session_info
.is_null());
335 callback
->onSuccess(new PresentationSessionClient(session_info
.Clone()));
336 presentation_service_
->ListenForSessionMessages(session_info
.Pass());
339 void PresentationDispatcher::OnSessionStateChanged(
340 presentation::PresentationSessionInfoPtr session_info
,
341 presentation::PresentationSessionState session_state
) {
345 DCHECK(!session_info
.is_null());
346 controller_
->didChangeSessionState(
347 new PresentationSessionClient(session_info
.Pass()),
348 GetWebPresentationSessionStateFromMojo(session_state
));
351 void PresentationDispatcher::OnSessionMessagesReceived(
352 presentation::PresentationSessionInfoPtr session_info
,
353 mojo::Array
<presentation::SessionMessagePtr
> messages
) {
357 for (size_t i
= 0; i
< messages
.size(); ++i
) {
358 // Note: Passing batches of messages to the Blink layer would be more
360 scoped_ptr
<PresentationSessionClient
> session_client(
361 new PresentationSessionClient(session_info
->url
, session_info
->id
));
362 switch (messages
[i
]->type
) {
363 case presentation::PresentationMessageType::
364 PRESENTATION_MESSAGE_TYPE_TEXT
: {
365 controller_
->didReceiveSessionTextMessage(
366 session_client
.release(),
367 blink::WebString::fromUTF8(messages
[i
]->message
));
370 case presentation::PresentationMessageType::
371 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
:
372 case presentation::PresentationMessageType::
373 PRESENTATION_MESSAGE_TYPE_BLOB
: {
374 controller_
->didReceiveSessionBinaryMessage(
375 session_client
.release(), &(messages
[i
]->data
.front()),
376 messages
[i
]->data
.size());
387 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
388 if (presentation_service_
.get())
391 render_frame()->GetServiceRegistry()->ConnectToRemoteService(
392 mojo::GetProxy(&presentation_service_
));
393 presentation::PresentationServiceClientPtr client_ptr
;
394 binding_
.Bind(GetProxy(&client_ptr
));
395 presentation_service_
->SetClient(client_ptr
.Pass());
397 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
398 &PresentationDispatcher::OnDefaultSessionStarted
,
399 base::Unretained(this)));
400 presentation_service_
->ListenForSessionStateChange();
403 void PresentationDispatcher::UpdateListeningState() {
404 bool should_listen
= !availability_callbacks_
.IsEmpty() ||
405 !availability_observers_
.empty();
406 bool is_listening
= listening_state_
!= ListeningState::Inactive
;
408 if (should_listen
== is_listening
)
411 ConnectToPresentationServiceIfNeeded();
413 listening_state_
= ListeningState::Waiting
;
414 presentation_service_
->ListenForScreenAvailability();
416 listening_state_
= ListeningState::Inactive
;
417 presentation_service_
->StopListeningForScreenAvailability();
421 PresentationDispatcher::SendMessageRequest::SendMessageRequest(
422 presentation::PresentationSessionInfoPtr session_info
,
423 presentation::SessionMessagePtr message
)
424 : session_info(session_info
.Pass()), message(message
.Pass()) {}
426 PresentationDispatcher::SendMessageRequest::~SendMessageRequest() {}
429 PresentationDispatcher::SendMessageRequest
*
430 PresentationDispatcher::CreateSendTextMessageRequest(
431 const blink::WebString
& presentationUrl
,
432 const blink::WebString
& presentationId
,
433 const blink::WebString
& message
) {
434 presentation::PresentationSessionInfoPtr session_info
=
435 presentation::PresentationSessionInfo::New();
436 session_info
->url
= presentationUrl
.utf8();
437 session_info
->id
= presentationId
.utf8();
439 presentation::SessionMessagePtr session_message
=
440 presentation::SessionMessage::New();
441 session_message
->type
=
442 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT
;
443 session_message
->message
= message
.utf8();
444 return new SendMessageRequest(session_info
.Pass(), session_message
.Pass());
448 PresentationDispatcher::SendMessageRequest
*
449 PresentationDispatcher::CreateSendBinaryMessageRequest(
450 const blink::WebString
& presentationUrl
,
451 const blink::WebString
& presentationId
,
452 presentation::PresentationMessageType type
,
455 presentation::PresentationSessionInfoPtr session_info
=
456 presentation::PresentationSessionInfo::New();
457 session_info
->url
= presentationUrl
.utf8();
458 session_info
->id
= presentationId
.utf8();
460 presentation::SessionMessagePtr session_message
=
461 presentation::SessionMessage::New();
462 session_message
->type
= type
;
463 std::vector
<uint8
> tmp_data_vector(data
, data
+ length
);
464 session_message
->data
.Swap(&tmp_data_vector
);
465 return new SendMessageRequest(session_info
.Pass(), session_message
.Pass());
468 } // namespace content