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"
7 #include "base/logging.h"
8 #include "content/common/presentation/presentation_service.mojom.h"
9 #include "content/public/common/presentation_constants.h"
10 #include "content/public/common/service_registry.h"
11 #include "content/public/renderer/render_frame.h"
12 #include "content/renderer/presentation/presentation_session_client.h"
13 #include "third_party/WebKit/public/platform/WebString.h"
14 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationController.h"
15 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationError.h"
16 #include "third_party/WebKit/public/web/WebDocument.h"
17 #include "third_party/WebKit/public/web/WebLocalFrame.h"
22 blink::WebPresentationError::ErrorType
GetWebPresentationErrorTypeFromMojo(
23 presentation::PresentationErrorType mojoErrorType
) {
24 switch (mojoErrorType
) {
25 case presentation::PRESENTATION_ERROR_TYPE_NO_AVAILABLE_SCREENS
:
26 return blink::WebPresentationError::ErrorTypeNoAvailableScreens
;
27 case presentation::PRESENTATION_ERROR_TYPE_SESSION_REQUEST_CANCELLED
:
28 return blink::WebPresentationError::ErrorTypeSessionRequestCancelled
;
29 case presentation::PRESENTATION_ERROR_TYPE_NO_PRESENTATION_FOUND
:
30 return blink::WebPresentationError::ErrorTypeNoPresentationFound
;
31 case presentation::PRESENTATION_ERROR_TYPE_UNKNOWN
:
33 return blink::WebPresentationError::ErrorTypeUnknown
;
37 blink::WebPresentationSessionState
GetWebPresentationSessionStateFromMojo(
38 presentation::PresentationSessionState mojoSessionState
) {
39 switch (mojoSessionState
) {
40 case presentation::PRESENTATION_SESSION_STATE_CONNECTED
:
41 return blink::WebPresentationSessionState::Connected
;
42 case presentation::PRESENTATION_SESSION_STATE_DISCONNECTED
:
43 return blink::WebPresentationSessionState::Disconnected
;
47 return blink::WebPresentationSessionState::Disconnected
;
50 GURL
GetPresentationURLFromFrame(content::RenderFrame
* frame
) {
53 GURL
url(frame
->GetWebFrame()->document().defaultPresentationURL());
54 return url
.is_valid() ? url
: GURL();
61 PresentationDispatcher::PresentationDispatcher(RenderFrame
* render_frame
)
62 : RenderFrameObserver(render_frame
),
67 PresentationDispatcher::~PresentationDispatcher() {
68 // Controller should be destroyed before the dispatcher when frame is
73 void PresentationDispatcher::setController(
74 blink::WebPresentationController
* controller
) {
75 // There shouldn't be any swapping from one non-null controller to another.
76 DCHECK(controller
!= controller_
&& (!controller
|| !controller_
));
77 controller_
= controller
;
78 // The controller is set to null when the frame is about to be detached.
79 // Nothing is listening for screen availability anymore but the Mojo service
80 // will know about the frame being detached anyway.
83 void PresentationDispatcher::updateAvailableChangeWatched(bool watched
) {
84 ConnectToPresentationServiceIfNeeded();
86 presentation_service_
->ListenForScreenAvailability();
88 presentation_service_
->StopListeningForScreenAvailability();
91 void PresentationDispatcher::startSession(
92 const blink::WebString
& presentationUrl
,
93 const blink::WebString
& presentationId
,
94 blink::WebPresentationSessionClientCallbacks
* callback
) {
96 ConnectToPresentationServiceIfNeeded();
98 // The dispatcher owns the service so |this| will be valid when
99 // OnSessionCreated() is called. |callback| needs to be alive and also needs
100 // to be destroyed so we transfer its ownership to the mojo callback.
101 presentation_service_
->StartSession(
102 presentationUrl
.utf8(),
103 presentationId
.utf8(),
104 base::Bind(&PresentationDispatcher::OnSessionCreated
,
105 base::Unretained(this),
106 base::Owned(callback
)));
109 void PresentationDispatcher::joinSession(
110 const blink::WebString
& presentationUrl
,
111 const blink::WebString
& presentationId
,
112 blink::WebPresentationSessionClientCallbacks
* callback
) {
114 ConnectToPresentationServiceIfNeeded();
116 // The dispatcher owns the service so |this| will be valid when
117 // OnSessionCreated() is called. |callback| needs to be alive and also needs
118 // to be destroyed so we transfer its ownership to the mojo callback.
119 presentation_service_
->JoinSession(
120 presentationUrl
.utf8(),
121 presentationId
.utf8(),
122 base::Bind(&PresentationDispatcher::OnSessionCreated
,
123 base::Unretained(this),
124 base::Owned(callback
)));
127 void PresentationDispatcher::sendString(
128 const blink::WebString
& presentationUrl
,
129 const blink::WebString
& presentationId
,
130 const blink::WebString
& message
) {
131 if (message
.utf8().size() > kMaxPresentationSessionMessageSize
) {
132 // TODO(crbug.com/459008): Limit the size of individual messages to 64k
133 // for now. Consider throwing DOMException or splitting bigger messages
134 // into smaller chunks later.
135 LOG(WARNING
) << "message size exceeded limit!";
139 presentation::SessionMessage
* session_message
=
140 new presentation::SessionMessage();
141 session_message
->presentation_url
= presentationUrl
.utf8();
142 session_message
->presentation_id
= presentationId
.utf8();
143 session_message
->type
= presentation::PresentationMessageType::
144 PRESENTATION_MESSAGE_TYPE_TEXT
;
145 session_message
->message
= message
.utf8();
147 message_request_queue_
.push(make_linked_ptr(session_message
));
148 // Start processing request if only one in the queue.
149 if (message_request_queue_
.size() == 1) {
150 const linked_ptr
<presentation::SessionMessage
>& request
=
151 message_request_queue_
.front();
152 DoSendMessage(*request
);
156 void PresentationDispatcher::sendArrayBuffer(
157 const blink::WebString
& presentationUrl
,
158 const blink::WebString
& presentationId
,
162 if (length
> kMaxPresentationSessionMessageSize
) {
163 // TODO(crbug.com/459008): Same as in sendString().
164 LOG(WARNING
) << "data size exceeded limit!";
168 const std::vector
<uint8
> vector(data
, data
+ length
);
169 presentation::SessionMessage
* session_message
=
170 new presentation::SessionMessage();
171 session_message
->presentation_url
= presentationUrl
.utf8();
172 session_message
->presentation_id
= presentationId
.utf8();
173 session_message
->type
= presentation::PresentationMessageType::
174 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
;
175 session_message
->data
= mojo::Array
<uint8
>::From(vector
);
177 message_request_queue_
.push(make_linked_ptr(session_message
));
178 // Start processing request if only one in the queue.
179 if (message_request_queue_
.size() == 1) {
180 const linked_ptr
<presentation::SessionMessage
>& request
=
181 message_request_queue_
.front();
182 DoSendMessage(*request
);
186 void PresentationDispatcher::DoSendMessage(
187 const presentation::SessionMessage
& session_message
) {
188 ConnectToPresentationServiceIfNeeded();
190 presentation::SessionMessagePtr
message_request(
191 presentation::SessionMessage::New());
192 message_request
->presentation_url
= session_message
.presentation_url
;
193 message_request
->presentation_id
= session_message
.presentation_id
;
194 message_request
->type
= session_message
.type
;
195 if (session_message
.type
== presentation::PresentationMessageType::
196 PRESENTATION_MESSAGE_TYPE_TEXT
) {
197 message_request
->message
= session_message
.message
;
198 } else if (session_message
.type
== presentation::PresentationMessageType::
199 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER
) {
200 message_request
->data
= mojo::Array
<uint8
>::From(
201 session_message
.data
.storage());
204 presentation_service_
->SendSessionMessage(
205 message_request
.Pass(),
206 base::Bind(&PresentationDispatcher::HandleSendMessageRequests
,
207 base::Unretained(this)));
210 void PresentationDispatcher::HandleSendMessageRequests(bool success
) {
211 // In normal cases, message_request_queue_ should not be empty at this point
212 // of time, but when DidCommitProvisionalLoad() is invoked before receiving
213 // the callback for previous send mojo call, queue would have been emptied.
214 if (message_request_queue_
.empty())
218 // PresentationServiceImpl is informing that Frame has been detached or
219 // navigated away. Invalidate all pending requests.
220 MessageRequestQueue empty
;
221 std::swap(message_request_queue_
, empty
);
225 message_request_queue_
.pop();
226 if (!message_request_queue_
.empty()) {
227 const linked_ptr
<presentation::SessionMessage
>& request
=
228 message_request_queue_
.front();
229 DoSendMessage(*request
);
233 void PresentationDispatcher::closeSession(
234 const blink::WebString
& presentationUrl
,
235 const blink::WebString
& presentationId
) {
236 ConnectToPresentationServiceIfNeeded();
238 presentation_service_
->CloseSession(
239 presentationUrl
.utf8(),
240 presentationId
.utf8());
243 void PresentationDispatcher::DidChangeDefaultPresentation() {
244 GURL
presentation_url(GetPresentationURLFromFrame(render_frame()));
246 ConnectToPresentationServiceIfNeeded();
247 presentation_service_
->SetDefaultPresentationURL(
248 presentation_url
.spec(), mojo::String());
251 void PresentationDispatcher::DidCommitProvisionalLoad(
252 bool is_new_navigation
,
253 bool is_same_page_navigation
) {
254 blink::WebFrame
* frame
= render_frame()->GetWebFrame();
255 // If not top-level navigation.
256 if (frame
->parent() || is_same_page_navigation
)
259 // Remove all pending send message requests.
260 MessageRequestQueue empty
;
261 std::swap(message_request_queue_
, empty
);
264 void PresentationDispatcher::OnScreenAvailabilityUpdated(bool available
) {
266 controller_
->didChangeAvailability(available
);
269 void PresentationDispatcher::OnDefaultSessionStarted(
270 presentation::PresentationSessionInfoPtr session_info
) {
274 // Reset the callback to get the next event.
275 presentation_service_
->ListenForDefaultSessionStart(base::Bind(
276 &PresentationDispatcher::OnDefaultSessionStarted
,
277 base::Unretained(this)));
279 if (!session_info
.is_null()) {
280 controller_
->didStartDefaultSession(
281 new PresentationSessionClient(session_info
.Pass()));
285 void PresentationDispatcher::OnSessionCreated(
286 blink::WebPresentationSessionClientCallbacks
* callback
,
287 presentation::PresentationSessionInfoPtr session_info
,
288 presentation::PresentationErrorPtr error
) {
290 if (!error
.is_null()) {
291 DCHECK(session_info
.is_null());
292 callback
->onError(new blink::WebPresentationError(
293 GetWebPresentationErrorTypeFromMojo(error
->error_type
),
294 blink::WebString::fromUTF8(error
->message
)));
298 DCHECK(!session_info
.is_null());
299 callback
->onSuccess(new PresentationSessionClient(session_info
.Pass()));
302 void PresentationDispatcher::OnSessionStateChange(
303 presentation::PresentationSessionInfoPtr session_info
,
304 presentation::PresentationSessionState session_state
) {
308 presentation_service_
->ListenForSessionStateChange(base::Bind(
309 &PresentationDispatcher::OnSessionStateChange
,
310 base::Unretained(this)));
312 DCHECK(!session_info
.is_null());
313 controller_
->didChangeSessionState(
314 new PresentationSessionClient(session_info
.Pass()),
315 GetWebPresentationSessionStateFromMojo(session_state
));
318 void PresentationDispatcher::OnSessionMessagesReceived(
319 mojo::Array
<presentation::SessionMessagePtr
> messages
) {
320 // When messages is null, there is an error at presentation service side.
321 if (!controller_
|| messages
.is_null())
324 for (size_t i
= 0; i
< messages
.size(); ++i
) {
325 if (messages
[i
]->type
==
326 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT
) {
327 controller_
->didReceiveSessionTextMessage(
328 new PresentationSessionClient(messages
[i
]->presentation_url
,
329 messages
[i
]->presentation_id
),
330 blink::WebString::fromUTF8(messages
[i
]->message
));
332 // TODO(haibinlu): handle binary message
336 presentation_service_
->ListenForSessionMessages(
337 base::Bind(&PresentationDispatcher::OnSessionMessagesReceived
,
338 base::Unretained(this)));
341 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
342 if (presentation_service_
.get())
345 render_frame()->GetServiceRegistry()->ConnectToRemoteService(
346 &presentation_service_
);
347 presentation::PresentationServiceClientPtr client_ptr
;
348 binding_
.Bind(GetProxy(&client_ptr
));
349 presentation_service_
->SetClient(client_ptr
.Pass());
351 // TODO(imcheng): Uncomment these once they are implemented on the browser
352 // side. (crbug.com/459006)
354 presentation_service_->ListenForDefaultSessionStart(base::Bind(
355 &PresentationDispatcher::OnDefaultSessionStarted,
356 base::Unretained(this)));
357 presentation_service_->ListenForSessionStateChange(base::Bind(
358 &PresentationDispatcher::OnSessionStateChange,
359 base::Unretained(this)));
360 presentation_service_->ListenForSessionMessages(
361 base::Bind(&PresentationDispatcher::OnSessionMessagesReceived,
362 base::Unretained(this)));
366 } // namespace content