Make USB permissions work in the new permission message system
[chromium-blink-merge.git] / content / renderer / presentation / presentation_dispatcher.cc
blob836b425cfc5eb4ecff68d694ec4260c9b1aec77f
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/WebURL.h"
18 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationAvailabilityObserver.h"
19 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationController.h"
20 #include "third_party/WebKit/public/platform/modules/presentation/WebPresentationError.h"
21 #include "third_party/WebKit/public/web/WebLocalFrame.h"
22 #include "url/gurl.h"
24 namespace {
26 blink::WebPresentationError::ErrorType GetWebPresentationErrorTypeFromMojo(
27 presentation::PresentationErrorType mojoErrorType) {
28 switch (mojoErrorType) {
29 case presentation::PRESENTATION_ERROR_TYPE_NO_AVAILABLE_SCREENS:
30 return blink::WebPresentationError::ErrorTypeNoAvailableScreens;
31 case presentation::PRESENTATION_ERROR_TYPE_SESSION_REQUEST_CANCELLED:
32 return blink::WebPresentationError::ErrorTypeSessionRequestCancelled;
33 case presentation::PRESENTATION_ERROR_TYPE_NO_PRESENTATION_FOUND:
34 return blink::WebPresentationError::ErrorTypeNoPresentationFound;
35 case presentation::PRESENTATION_ERROR_TYPE_UNKNOWN:
36 default:
37 return blink::WebPresentationError::ErrorTypeUnknown;
41 blink::WebPresentationSessionState GetWebPresentationSessionStateFromMojo(
42 presentation::PresentationSessionState mojoSessionState) {
43 switch (mojoSessionState) {
44 case presentation::PRESENTATION_SESSION_STATE_CONNECTED:
45 return blink::WebPresentationSessionState::Connected;
46 case presentation::PRESENTATION_SESSION_STATE_DISCONNECTED:
47 return blink::WebPresentationSessionState::Disconnected;
50 NOTREACHED();
51 return blink::WebPresentationSessionState::Disconnected;
54 } // namespace
56 namespace content {
58 PresentationDispatcher::PresentationDispatcher(RenderFrame* render_frame)
59 : RenderFrameObserver(render_frame),
60 controller_(nullptr),
61 binding_(this) {
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::startSession(
81 const blink::WebString& presentationUrl,
82 blink::WebPresentationSessionClientCallbacks* callback) {
83 DCHECK(callback);
84 ConnectToPresentationServiceIfNeeded();
86 // The dispatcher owns the service so |this| will be valid when
87 // OnSessionCreated() is called. |callback| needs to be alive and also needs
88 // to be destroyed so we transfer its ownership to the mojo callback.
89 presentation_service_->StartSession(
90 presentationUrl.utf8(),
91 base::Bind(&PresentationDispatcher::OnSessionCreated,
92 base::Unretained(this),
93 base::Owned(callback)));
96 void PresentationDispatcher::joinSession(
97 const blink::WebString& presentationUrl,
98 const blink::WebString& presentationId,
99 blink::WebPresentationSessionClientCallbacks* callback) {
100 DCHECK(callback);
101 ConnectToPresentationServiceIfNeeded();
103 // The dispatcher owns the service so |this| will be valid when
104 // OnSessionCreated() is called. |callback| needs to be alive and also needs
105 // to be destroyed so we transfer its ownership to the mojo callback.
106 presentation_service_->JoinSession(
107 presentationUrl.utf8(),
108 presentationId.utf8(),
109 base::Bind(&PresentationDispatcher::OnSessionCreated,
110 base::Unretained(this),
111 base::Owned(callback)));
114 void PresentationDispatcher::sendString(
115 const blink::WebString& presentationUrl,
116 const blink::WebString& presentationId,
117 const blink::WebString& message) {
118 if (message.utf8().size() > kMaxPresentationSessionMessageSize) {
119 // TODO(crbug.com/459008): Limit the size of individual messages to 64k
120 // for now. Consider throwing DOMException or splitting bigger messages
121 // into smaller chunks later.
122 LOG(WARNING) << "message size exceeded limit!";
123 return;
126 message_request_queue_.push(make_linked_ptr(
127 CreateSendTextMessageRequest(presentationUrl, presentationId, message)));
128 // Start processing request if only one in the queue.
129 if (message_request_queue_.size() == 1)
130 DoSendMessage(message_request_queue_.front().get());
133 void PresentationDispatcher::sendArrayBuffer(
134 const blink::WebString& presentationUrl,
135 const blink::WebString& presentationId,
136 const uint8* data,
137 size_t length) {
138 DCHECK(data);
139 if (length > kMaxPresentationSessionMessageSize) {
140 // TODO(crbug.com/459008): Same as in sendString().
141 LOG(WARNING) << "data size exceeded limit!";
142 return;
145 message_request_queue_.push(make_linked_ptr(
146 CreateSendBinaryMessageRequest(presentationUrl, presentationId,
147 presentation::PresentationMessageType::
148 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER,
149 data, length)));
150 // Start processing request if only one in the queue.
151 if (message_request_queue_.size() == 1)
152 DoSendMessage(message_request_queue_.front().get());
155 void PresentationDispatcher::sendBlobData(
156 const blink::WebString& presentationUrl,
157 const blink::WebString& presentationId,
158 const uint8* data,
159 size_t length) {
160 DCHECK(data);
161 if (length > kMaxPresentationSessionMessageSize) {
162 // TODO(crbug.com/459008): Same as in sendString().
163 LOG(WARNING) << "data size exceeded limit!";
164 return;
167 message_request_queue_.push(make_linked_ptr(CreateSendBinaryMessageRequest(
168 presentationUrl, presentationId,
169 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_BLOB,
170 data, length)));
171 // Start processing request if only one in the queue.
172 if (message_request_queue_.size() == 1)
173 DoSendMessage(message_request_queue_.front().get());
176 void PresentationDispatcher::DoSendMessage(SendMessageRequest* request) {
177 ConnectToPresentationServiceIfNeeded();
179 presentation_service_->SendSessionMessage(
180 request->session_info.Pass(), request->message.Pass(),
181 base::Bind(&PresentationDispatcher::HandleSendMessageRequests,
182 base::Unretained(this)));
185 void PresentationDispatcher::HandleSendMessageRequests(bool success) {
186 // In normal cases, message_request_queue_ should not be empty at this point
187 // of time, but when DidCommitProvisionalLoad() is invoked before receiving
188 // the callback for previous send mojo call, queue would have been emptied.
189 if (message_request_queue_.empty())
190 return;
192 if (!success) {
193 // PresentationServiceImpl is informing that Frame has been detached or
194 // navigated away. Invalidate all pending requests.
195 MessageRequestQueue empty;
196 std::swap(message_request_queue_, empty);
197 return;
200 message_request_queue_.pop();
201 if (!message_request_queue_.empty()) {
202 DoSendMessage(message_request_queue_.front().get());
206 void PresentationDispatcher::closeSession(
207 const blink::WebString& presentationUrl,
208 const blink::WebString& presentationId) {
209 ConnectToPresentationServiceIfNeeded();
211 presentation_service_->CloseSession(
212 presentationUrl.utf8(),
213 presentationId.utf8());
216 void PresentationDispatcher::getAvailability(
217 const blink::WebString& availabilityUrl,
218 blink::WebPresentationAvailabilityCallbacks* callbacks) {
219 const std::string& availability_url = availabilityUrl.utf8();
220 AvailabilityStatus* status = nullptr;
221 auto status_it = availability_status_.find(availability_url);
222 if (status_it == availability_status_.end()) {
223 status = new AvailabilityStatus(availability_url);
224 availability_status_.set(availability_url, make_scoped_ptr(status));
225 } else {
226 status = status_it->second;
228 DCHECK(status);
230 if (status->listening_state == ListeningState::ACTIVE) {
231 callbacks->onSuccess(status->last_known_availability);
232 delete callbacks;
233 return;
236 status->availability_callbacks.Add(callbacks);
237 UpdateListeningState(status);
240 void PresentationDispatcher::startListening(
241 blink::WebPresentationAvailabilityObserver* observer) {
242 const std::string& availability_url = observer->url().string().utf8();
243 auto status_it = availability_status_.find(availability_url);
244 if (status_it == availability_status_.end()) {
245 DLOG(WARNING) << "Start listening for availability for unknown URL "
246 << availability_url;
247 return;
249 status_it->second->availability_observers.insert(observer);
250 UpdateListeningState(status_it->second);
253 void PresentationDispatcher::stopListening(
254 blink::WebPresentationAvailabilityObserver* observer) {
255 const std::string& availability_url = observer->url().string().utf8();
256 auto status_it = availability_status_.find(availability_url);
257 if (status_it == availability_status_.end()) {
258 DLOG(WARNING) << "Stop listening for availability for unknown URL "
259 << availability_url;
260 return;
262 status_it->second->availability_observers.erase(observer);
263 UpdateListeningState(status_it->second);
266 void PresentationDispatcher::setDefaultPresentationUrl(
267 const blink::WebString& url) {
268 ConnectToPresentationServiceIfNeeded();
269 presentation_service_->SetDefaultPresentationURL(url.utf8());
272 void PresentationDispatcher::DidCommitProvisionalLoad(
273 bool is_new_navigation,
274 bool is_same_page_navigation) {
275 blink::WebFrame* frame = render_frame()->GetWebFrame();
276 // If not top-level navigation.
277 if (frame->parent() || is_same_page_navigation)
278 return;
280 // Remove all pending send message requests.
281 MessageRequestQueue empty;
282 std::swap(message_request_queue_, empty);
285 void PresentationDispatcher::OnScreenAvailabilityUpdated(
286 const mojo::String& url, bool available) {
287 const std::string& availability_url = url.get();
288 auto status_it = availability_status_.find(availability_url);
289 if (status_it == availability_status_.end())
290 return;
291 AvailabilityStatus* status = status_it->second;
292 DCHECK(status);
294 if (status->listening_state == ListeningState::WAITING)
295 status->listening_state = ListeningState::ACTIVE;
297 for (auto observer : status->availability_observers)
298 observer->availabilityChanged(available);
300 for (AvailabilityCallbacksMap::iterator iter(&status->availability_callbacks);
301 !iter.IsAtEnd(); iter.Advance()) {
302 iter.GetCurrentValue()->onSuccess(available);
304 status->last_known_availability = available;
305 status->availability_callbacks.Clear();
306 UpdateListeningState(status);
309 void PresentationDispatcher::OnScreenAvailabilityNotSupported(
310 const mojo::String& url) {
311 const std::string& availability_url = url.get();
312 auto status_it = availability_status_.find(availability_url);
313 if (status_it == availability_status_.end())
314 return;
315 AvailabilityStatus* status = status_it->second;
316 DCHECK(status);
317 DCHECK(status->listening_state == ListeningState::WAITING);
319 const blink::WebString& not_supported_error = blink::WebString::fromUTF8(
320 "getAvailability() isn't supported at the moment. It can be due to "
321 "a permanent or temporary system limitation. It is recommended to "
322 "try to blindly start a session in that case.");
323 for (AvailabilityCallbacksMap::iterator iter(&status->availability_callbacks);
324 !iter.IsAtEnd(); iter.Advance()) {
325 iter.GetCurrentValue()->onError(blink::WebPresentationError(
326 blink::WebPresentationError::ErrorTypeAvailabilityNotSupported,
327 not_supported_error));
329 status->availability_callbacks.Clear();
330 UpdateListeningState(status);
333 void PresentationDispatcher::OnDefaultSessionStarted(
334 presentation::PresentationSessionInfoPtr session_info) {
335 if (!controller_)
336 return;
338 // Reset the callback to get the next event.
339 presentation_service_->ListenForDefaultSessionStart(base::Bind(
340 &PresentationDispatcher::OnDefaultSessionStarted,
341 base::Unretained(this)));
343 if (!session_info.is_null()) {
344 controller_->didStartDefaultSession(
345 new PresentationSessionClient(session_info.Clone()));
346 presentation_service_->ListenForSessionMessages(session_info.Pass());
350 void PresentationDispatcher::OnSessionCreated(
351 blink::WebPresentationSessionClientCallbacks* callback,
352 presentation::PresentationSessionInfoPtr session_info,
353 presentation::PresentationErrorPtr error) {
354 DCHECK(callback);
355 if (!error.is_null()) {
356 DCHECK(session_info.is_null());
357 callback->onError(blink::WebPresentationError(
358 GetWebPresentationErrorTypeFromMojo(error->error_type),
359 blink::WebString::fromUTF8(error->message)));
360 return;
363 DCHECK(!session_info.is_null());
364 callback->onSuccess(
365 blink::adoptWebPtr(new PresentationSessionClient(session_info.Clone())));
366 presentation_service_->ListenForSessionMessages(session_info.Pass());
369 void PresentationDispatcher::OnSessionStateChanged(
370 presentation::PresentationSessionInfoPtr session_info,
371 presentation::PresentationSessionState session_state) {
372 if (!controller_)
373 return;
375 DCHECK(!session_info.is_null());
376 controller_->didChangeSessionState(
377 new PresentationSessionClient(session_info.Pass()),
378 GetWebPresentationSessionStateFromMojo(session_state));
381 void PresentationDispatcher::OnSessionMessagesReceived(
382 presentation::PresentationSessionInfoPtr session_info,
383 mojo::Array<presentation::SessionMessagePtr> messages) {
384 if (!controller_)
385 return;
387 for (size_t i = 0; i < messages.size(); ++i) {
388 // Note: Passing batches of messages to the Blink layer would be more
389 // efficient.
390 scoped_ptr<PresentationSessionClient> session_client(
391 new PresentationSessionClient(session_info->url, session_info->id));
392 switch (messages[i]->type) {
393 case presentation::PresentationMessageType::
394 PRESENTATION_MESSAGE_TYPE_TEXT: {
395 controller_->didReceiveSessionTextMessage(
396 session_client.release(),
397 blink::WebString::fromUTF8(messages[i]->message));
398 break;
400 case presentation::PresentationMessageType::
401 PRESENTATION_MESSAGE_TYPE_ARRAY_BUFFER:
402 case presentation::PresentationMessageType::
403 PRESENTATION_MESSAGE_TYPE_BLOB: {
404 controller_->didReceiveSessionBinaryMessage(
405 session_client.release(), &(messages[i]->data.front()),
406 messages[i]->data.size());
407 break;
409 default: {
410 NOTREACHED();
411 break;
417 void PresentationDispatcher::ConnectToPresentationServiceIfNeeded() {
418 if (presentation_service_.get())
419 return;
421 render_frame()->GetServiceRegistry()->ConnectToRemoteService(
422 mojo::GetProxy(&presentation_service_));
423 presentation::PresentationServiceClientPtr client_ptr;
424 binding_.Bind(GetProxy(&client_ptr));
425 presentation_service_->SetClient(client_ptr.Pass());
427 presentation_service_->ListenForDefaultSessionStart(base::Bind(
428 &PresentationDispatcher::OnDefaultSessionStarted,
429 base::Unretained(this)));
430 presentation_service_->ListenForSessionStateChange();
433 void PresentationDispatcher::UpdateListeningState(AvailabilityStatus* status) {
434 bool should_listen = !status->availability_callbacks.IsEmpty() ||
435 !status->availability_observers.empty();
436 bool is_listening = status->listening_state != ListeningState::INACTIVE;
438 if (should_listen == is_listening)
439 return;
441 ConnectToPresentationServiceIfNeeded();
442 if (should_listen) {
443 status->listening_state = ListeningState::WAITING;
444 presentation_service_->ListenForScreenAvailability(status->url);
445 } else {
446 status->listening_state = ListeningState::INACTIVE;
447 presentation_service_->StopListeningForScreenAvailability(status->url);
451 PresentationDispatcher::SendMessageRequest::SendMessageRequest(
452 presentation::PresentationSessionInfoPtr session_info,
453 presentation::SessionMessagePtr message)
454 : session_info(session_info.Pass()), message(message.Pass()) {}
456 PresentationDispatcher::SendMessageRequest::~SendMessageRequest() {}
458 // static
459 PresentationDispatcher::SendMessageRequest*
460 PresentationDispatcher::CreateSendTextMessageRequest(
461 const blink::WebString& presentationUrl,
462 const blink::WebString& presentationId,
463 const blink::WebString& message) {
464 presentation::PresentationSessionInfoPtr session_info =
465 presentation::PresentationSessionInfo::New();
466 session_info->url = presentationUrl.utf8();
467 session_info->id = presentationId.utf8();
469 presentation::SessionMessagePtr session_message =
470 presentation::SessionMessage::New();
471 session_message->type =
472 presentation::PresentationMessageType::PRESENTATION_MESSAGE_TYPE_TEXT;
473 session_message->message = message.utf8();
474 return new SendMessageRequest(session_info.Pass(), session_message.Pass());
477 // static
478 PresentationDispatcher::SendMessageRequest*
479 PresentationDispatcher::CreateSendBinaryMessageRequest(
480 const blink::WebString& presentationUrl,
481 const blink::WebString& presentationId,
482 presentation::PresentationMessageType type,
483 const uint8* data,
484 size_t length) {
485 presentation::PresentationSessionInfoPtr session_info =
486 presentation::PresentationSessionInfo::New();
487 session_info->url = presentationUrl.utf8();
488 session_info->id = presentationId.utf8();
490 presentation::SessionMessagePtr session_message =
491 presentation::SessionMessage::New();
492 session_message->type = type;
493 std::vector<uint8> tmp_data_vector(data, data + length);
494 session_message->data.Swap(&tmp_data_vector);
495 return new SendMessageRequest(session_info.Pass(), session_message.Pass());
498 PresentationDispatcher::AvailabilityStatus::AvailabilityStatus(
499 const std::string& availability_url)
500 : url(availability_url),
501 last_known_availability(false),
502 listening_state(ListeningState::INACTIVE) {}
504 PresentationDispatcher::AvailabilityStatus::~AvailabilityStatus() {
507 } // namespace content