Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / content / renderer / presentation / presentation_dispatcher.cc
blob31c19d3746dc6a9e478ec0e144238fd5f5b9fd28
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 presentation::SessionMessage* GetMojoSessionMessage(
54 const blink::WebString& presentationUrl,
55 const blink::WebString& presentationId,
56 presentation::PresentationMessageType type,
57 const uint8* data,
58 size_t length) {
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;
69 } // namespace
71 namespace content {
73 PresentationDispatcher::PresentationDispatcher(RenderFrame* render_frame)
74 : RenderFrameObserver(render_frame),
75 controller_(nullptr),
76 binding_(this),
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
84 // destroyed.
85 DCHECK(!controller_);
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();
100 if (watched)
101 presentation_service_->ListenForScreenAvailability();
102 else
103 presentation_service_->StopListeningForScreenAvailability();
106 void PresentationDispatcher::startSession(
107 const blink::WebString& presentationUrl,
108 blink::WebPresentationSessionClientCallbacks* callback) {
109 DCHECK(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) {
126 DCHECK(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!";
149 return;
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,
172 const uint8* data,
173 size_t length) {
174 DCHECK(data);
175 if (length > kMaxPresentationSessionMessageSize) {
176 // TODO(crbug.com/459008): Same as in sendString().
177 LOG(WARNING) << "data size exceeded limit!";
178 return;
181 presentation::SessionMessage* session_message =
182 GetMojoSessionMessage(presentationUrl, presentationId,
183 presentation::PresentationMessageType::
184 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER,
185 data, length);
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,
198 const uint8* data,
199 size_t length) {
200 DCHECK(data);
201 if (length > kMaxPresentationSessionMessageSize) {
202 // TODO(crbug.com/459008): Same as in sendString().
203 LOG(WARNING) << "data size exceeded limit!";
204 return;
207 presentation::SessionMessage* session_message = GetMojoSessionMessage(
208 presentationUrl, presentationId,
209 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_BLOB,
210 data, length);
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;
231 break;
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());
239 break;
241 default: {
242 NOTREACHED() << "Invalid presentation message type "
243 << session_message.type;
244 break;
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())
259 return;
261 if (!success) {
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);
266 return;
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_);
292 delete callbacks;
293 return;
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)
325 return;
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) {
371 if (!controller_)
372 return;
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) {
390 DCHECK(callback);
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)));
396 return;
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_)
406 return;
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) {
417 if (!controller_)
418 return;
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;
434 return;
437 for (size_t i = 0; i < messages.size(); ++i) {
438 // Note: Passing batches of messages to the Blink layer would be more
439 // efficient.
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));
449 break;
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());
458 break;
460 default: {
461 NOTREACHED();
462 break;
467 presentation_service_->ListenForSessionMessages(
468 base::Bind(&PresentationDispatcher::OnSessionMessagesReceived,
469 base::Unretained(this)));
472 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
473 if (presentation_service_.get())
474 return;
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)
494 return;
496 ConnectToPresentationServiceIfNeeded();
497 if (should_listen) {
498 listening_state_ = ListeningState::Waiting;
499 presentation_service_->ListenForScreenAvailability();
500 } else {
501 listening_state_ = ListeningState::Inactive;
502 presentation_service_->StopListeningForScreenAvailability();
506 } // namespace content