Add ICU message format support
[chromium-blink-merge.git] / content / renderer / presentation / presentation_dispatcher.cc
blob74b4dca5d6d955a2a08094e750195c997e1d54e3
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 <algorithm>
8 #include <vector>
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"
21 #include "url/gurl.h"
23 namespace {
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:
35 default:
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;
49 NOTREACHED();
50 return blink::WebPresentationSessionState::Disconnected;
53 } // namespace
55 namespace content {
57 PresentationDispatcher::PresentationDispatcher(RenderFrame* render_frame)
58 : RenderFrameObserver(render_frame),
59 controller_(nullptr),
60 binding_(this),
61 listening_state_(ListeningState::Inactive),
62 last_known_availability_(false) {}
64 PresentationDispatcher::~PresentationDispatcher() {
65 // Controller should be destroyed before the dispatcher when frame is
66 // destroyed.
67 DCHECK(!controller_);
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();
82 if (watched)
83 presentation_service_->ListenForScreenAvailability();
84 else
85 presentation_service_->StopListeningForScreenAvailability();
88 void PresentationDispatcher::startSession(
89 const blink::WebString& presentationUrl,
90 blink::WebPresentationSessionClientCallbacks* callback) {
91 DCHECK(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) {
108 DCHECK(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!";
131 return;
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,
144 const uint8* data,
145 size_t length) {
146 DCHECK(data);
147 if (length > kMaxPresentationSessionMessageSize) {
148 // TODO(crbug.com/459008): Same as in sendString().
149 LOG(WARNING) << "data size exceeded limit!";
150 return;
153 message_request_queue_.push(make_linked_ptr(
154 CreateSendBinaryMessageRequest(presentationUrl, presentationId,
155 presentation::PresentationMessageType::
156 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER,
157 data, length)));
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,
166 const uint8* data,
167 size_t length) {
168 DCHECK(data);
169 if (length > kMaxPresentationSessionMessageSize) {
170 // TODO(crbug.com/459008): Same as in sendString().
171 LOG(WARNING) << "data size exceeded limit!";
172 return;
175 message_request_queue_.push(make_linked_ptr(CreateSendBinaryMessageRequest(
176 presentationUrl, presentationId,
177 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_BLOB,
178 data, length)));
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())
198 return;
200 if (!success) {
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);
205 return;
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_));
229 delete callbacks;
230 return;
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)
262 return;
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) {
306 if (!controller_)
307 return;
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) {
325 DCHECK(callback);
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)));
331 return;
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) {
342 if (!controller_)
343 return;
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) {
354 if (!controller_)
355 return;
357 for (size_t i = 0; i < messages.size(); ++i) {
358 // Note: Passing batches of messages to the Blink layer would be more
359 // efficient.
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));
368 break;
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());
377 break;
379 default: {
380 NOTREACHED();
381 break;
387 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
388 if (presentation_service_.get())
389 return;
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)
409 return;
411 ConnectToPresentationServiceIfNeeded();
412 if (should_listen) {
413 listening_state_ = ListeningState::Waiting;
414 presentation_service_->ListenForScreenAvailability();
415 } else {
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() {}
428 // static
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());
447 // static
448 PresentationDispatcher::SendMessageRequest*
449 PresentationDispatcher::CreateSendBinaryMessageRequest(
450 const blink::WebString& presentationUrl,
451 const blink::WebString& presentationId,
452 presentation::PresentationMessageType type,
453 const uint8* data,
454 size_t length) {
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