|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "content/browser/permissions/permission_service_impl.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/permission_manager.h" | 
|  | #include "content/public/browser/permission_type.h" | 
|  |  | 
|  | using blink::mojom::PermissionDescriptorPtr; | 
|  | using blink::mojom::PermissionName; | 
|  | using blink::mojom::PermissionObserverPtr; | 
|  | using blink::mojom::PermissionStatus; | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | PermissionType PermissionDescriptorToPermissionType( | 
|  | const PermissionDescriptorPtr& descriptor) { | 
|  | switch (descriptor->name) { | 
|  | case PermissionName::GEOLOCATION: | 
|  | return PermissionType::GEOLOCATION; | 
|  | case PermissionName::NOTIFICATIONS: | 
|  | return PermissionType::NOTIFICATIONS; | 
|  | case PermissionName::PUSH_NOTIFICATIONS: | 
|  | return PermissionType::PUSH_MESSAGING; | 
|  | case PermissionName::MIDI: { | 
|  | if (descriptor->extension && descriptor->extension->is_midi() && | 
|  | descriptor->extension->get_midi()->sysex) { | 
|  | return PermissionType::MIDI_SYSEX; | 
|  | } | 
|  | return PermissionType::MIDI; | 
|  | } | 
|  | case PermissionName::PROTECTED_MEDIA_IDENTIFIER: | 
|  | return PermissionType::PROTECTED_MEDIA_IDENTIFIER; | 
|  | case PermissionName::DURABLE_STORAGE: | 
|  | return PermissionType::DURABLE_STORAGE; | 
|  | case PermissionName::AUDIO_CAPTURE: | 
|  | return PermissionType::AUDIO_CAPTURE; | 
|  | case PermissionName::VIDEO_CAPTURE: | 
|  | return PermissionType::VIDEO_CAPTURE; | 
|  | case PermissionName::BACKGROUND_SYNC: | 
|  | return PermissionType::BACKGROUND_SYNC; | 
|  | } | 
|  |  | 
|  | NOTREACHED(); | 
|  | return PermissionType::NUM; | 
|  | } | 
|  |  | 
|  | // This function allows the usage of the the multiple request map | 
|  | // with single requests. | 
|  | void PermissionRequestResponseCallbackWrapper( | 
|  | const base::Callback<void(PermissionStatus)>& callback, | 
|  | const std::vector<PermissionStatus>& vector) { | 
|  | DCHECK_EQ(vector.size(), 1ul); | 
|  | callback.Run(vector[0]); | 
|  | } | 
|  |  | 
|  | } // anonymous namespace | 
|  |  | 
|  | PermissionServiceImpl::PendingRequest::PendingRequest( | 
|  | const RequestPermissionsCallback& callback, | 
|  | int request_count) | 
|  | : callback(callback), | 
|  | request_count(request_count) { | 
|  | } | 
|  |  | 
|  | PermissionServiceImpl::PendingRequest::~PendingRequest() { | 
|  | if (callback.is_null()) | 
|  | return; | 
|  |  | 
|  | std::vector<PermissionStatus> result(request_count, PermissionStatus::DENIED); | 
|  | callback.Run(result); | 
|  | } | 
|  |  | 
|  | PermissionServiceImpl::PermissionServiceImpl( | 
|  | PermissionServiceContext* context, | 
|  | mojo::InterfaceRequest<blink::mojom::PermissionService> request) | 
|  | : context_(context), | 
|  | binding_(this, std::move(request)), | 
|  | weak_factory_(this) { | 
|  | binding_.set_connection_error_handler( | 
|  | base::Bind(&PermissionServiceImpl::OnConnectionError, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | PermissionServiceImpl::~PermissionServiceImpl() { | 
|  | DCHECK(pending_requests_.IsEmpty()); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::OnConnectionError() { | 
|  | CancelPendingOperations(); | 
|  | context_->ServiceHadConnectionError(this); | 
|  | // After that call, |this| will be deleted. | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::RequestPermission( | 
|  | PermissionDescriptorPtr permission, | 
|  | const url::Origin& origin, | 
|  | bool user_gesture, | 
|  | const PermissionStatusCallback& callback) { | 
|  | // This condition is valid if the call is coming from a ChildThread instead of | 
|  | // a RenderFrame. Some consumers of the service run in Workers and some in | 
|  | // Frames. In the context of a Worker, it is not possible to show a | 
|  | // permission prompt because there is no tab. In the context of a Frame, we | 
|  | // can. Even if the call comes from a context where it is not possible to show | 
|  | // any UI, we want to still return something relevant so the current | 
|  | // permission status is returned. | 
|  | BrowserContext* browser_context = context_->GetBrowserContext(); | 
|  | DCHECK(browser_context); | 
|  | if (!context_->render_frame_host() || | 
|  | !browser_context->GetPermissionManager()) { | 
|  | callback.Run(GetPermissionStatus(permission, origin)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | int pending_request_id = | 
|  | pending_requests_.Add(base::MakeUnique<PendingRequest>( | 
|  | base::Bind(&PermissionRequestResponseCallbackWrapper, callback), 1)); | 
|  | int id = browser_context->GetPermissionManager()->RequestPermission( | 
|  | PermissionDescriptorToPermissionType(permission), | 
|  | context_->render_frame_host(), origin.GetURL(), user_gesture, | 
|  | base::Bind(&PermissionServiceImpl::OnRequestPermissionResponse, | 
|  | weak_factory_.GetWeakPtr(), pending_request_id)); | 
|  |  | 
|  | // Check if the request still exists. It might have been removed by the | 
|  | // callback if it was run synchronously. | 
|  | PendingRequest* pending_request = pending_requests_.Lookup( | 
|  | pending_request_id); | 
|  | if (!pending_request) | 
|  | return; | 
|  | pending_request->id = id; | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::OnRequestPermissionResponse( | 
|  | int pending_request_id, | 
|  | PermissionStatus status) { | 
|  | OnRequestPermissionsResponse(pending_request_id, | 
|  | std::vector<PermissionStatus>(1, status)); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::RequestPermissions( | 
|  | std::vector<PermissionDescriptorPtr> permissions, | 
|  | const url::Origin& origin, | 
|  | bool user_gesture, | 
|  | const RequestPermissionsCallback& callback) { | 
|  | // This condition is valid if the call is coming from a ChildThread instead of | 
|  | // a RenderFrame. Some consumers of the service run in Workers and some in | 
|  | // Frames. In the context of a Worker, it is not possible to show a | 
|  | // permission prompt because there is no tab. In the context of a Frame, we | 
|  | // can. Even if the call comes from a context where it is not possible to show | 
|  | // any UI, we want to still return something relevant so the current | 
|  | // permission status is returned for each permission. | 
|  | BrowserContext* browser_context = context_->GetBrowserContext(); | 
|  | DCHECK(browser_context); | 
|  | if (!context_->render_frame_host() || | 
|  | !browser_context->GetPermissionManager()) { | 
|  | std::vector<PermissionStatus> result(permissions.size()); | 
|  | for (size_t i = 0; i < permissions.size(); ++i) | 
|  | result[i] = GetPermissionStatus(permissions[i], origin); | 
|  | callback.Run(result); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::vector<PermissionType> types(permissions.size()); | 
|  | for (size_t i = 0; i < types.size(); ++i) | 
|  | types[i] = PermissionDescriptorToPermissionType(permissions[i]); | 
|  |  | 
|  | int pending_request_id = pending_requests_.Add( | 
|  | base::MakeUnique<PendingRequest>(callback, permissions.size())); | 
|  | int id = browser_context->GetPermissionManager()->RequestPermissions( | 
|  | types, context_->render_frame_host(), origin.GetURL(), user_gesture, | 
|  | base::Bind(&PermissionServiceImpl::OnRequestPermissionsResponse, | 
|  | weak_factory_.GetWeakPtr(), pending_request_id)); | 
|  |  | 
|  | // Check if the request still exists. It may have been removed by the | 
|  | // the response callback. | 
|  | PendingRequest* pending_request = pending_requests_.Lookup( | 
|  | pending_request_id); | 
|  | if (!pending_request) | 
|  | return; | 
|  | pending_request->id = id; | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::OnRequestPermissionsResponse( | 
|  | int pending_request_id, | 
|  | const std::vector<PermissionStatus>& result) { | 
|  | PendingRequest* request = pending_requests_.Lookup(pending_request_id); | 
|  | RequestPermissionsCallback callback(request->callback); | 
|  | request->callback.Reset(); | 
|  | pending_requests_.Remove(pending_request_id); | 
|  | callback.Run(result); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::CancelPendingOperations() { | 
|  | DCHECK(context_->GetBrowserContext()); | 
|  |  | 
|  | PermissionManager* permission_manager = | 
|  | context_->GetBrowserContext()->GetPermissionManager(); | 
|  | if (!permission_manager) | 
|  | return; | 
|  |  | 
|  | // Cancel pending requests. | 
|  | for (RequestsMap::Iterator<PendingRequest> it(&pending_requests_); | 
|  | !it.IsAtEnd(); it.Advance()) { | 
|  | permission_manager->CancelPermissionRequest( | 
|  | it.GetCurrentValue()->id); | 
|  | } | 
|  | pending_requests_.Clear(); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::HasPermission( | 
|  | PermissionDescriptorPtr permission, | 
|  | const url::Origin& origin, | 
|  | const PermissionStatusCallback& callback) { | 
|  | callback.Run(GetPermissionStatus(permission, origin)); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::RevokePermission( | 
|  | PermissionDescriptorPtr permission, | 
|  | const url::Origin& origin, | 
|  | const PermissionStatusCallback& callback) { | 
|  | PermissionType permission_type = | 
|  | PermissionDescriptorToPermissionType(permission); | 
|  | PermissionStatus status = | 
|  | GetPermissionStatusFromType(permission_type, origin); | 
|  |  | 
|  | // Resetting the permission should only be possible if the permission is | 
|  | // already granted. | 
|  | if (status != PermissionStatus::GRANTED) { | 
|  | callback.Run(status); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ResetPermissionStatus(permission_type, origin); | 
|  |  | 
|  | callback.Run(GetPermissionStatusFromType(permission_type, origin)); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::AddPermissionObserver( | 
|  | PermissionDescriptorPtr permission, | 
|  | const url::Origin& origin, | 
|  | PermissionStatus last_known_status, | 
|  | PermissionObserverPtr observer) { | 
|  | PermissionStatus current_status = GetPermissionStatus(permission, origin); | 
|  | if (current_status != last_known_status) { | 
|  | observer->OnPermissionStatusChange(current_status); | 
|  | last_known_status = current_status; | 
|  | } | 
|  |  | 
|  | context_->CreateSubscription(PermissionDescriptorToPermissionType(permission), | 
|  | origin, std::move(observer)); | 
|  | } | 
|  |  | 
|  | PermissionStatus PermissionServiceImpl::GetPermissionStatus( | 
|  | const PermissionDescriptorPtr& permission, | 
|  | const url::Origin& origin) { | 
|  | return GetPermissionStatusFromType( | 
|  | PermissionDescriptorToPermissionType(permission), origin); | 
|  | } | 
|  |  | 
|  | PermissionStatus PermissionServiceImpl::GetPermissionStatusFromType( | 
|  | PermissionType type, | 
|  | const url::Origin& origin) { | 
|  | BrowserContext* browser_context = context_->GetBrowserContext(); | 
|  | DCHECK(browser_context); | 
|  | if (!browser_context->GetPermissionManager()) | 
|  | return PermissionStatus::DENIED; | 
|  |  | 
|  | GURL requesting_origin(origin.Serialize()); | 
|  | // If the embedding_origin is empty we'll use |origin| instead. | 
|  | GURL embedding_origin = context_->GetEmbeddingOrigin(); | 
|  | return browser_context->GetPermissionManager()->GetPermissionStatus( | 
|  | type, requesting_origin, | 
|  | embedding_origin.is_empty() ? requesting_origin : embedding_origin); | 
|  | } | 
|  |  | 
|  | void PermissionServiceImpl::ResetPermissionStatus(PermissionType type, | 
|  | const url::Origin& origin) { | 
|  | BrowserContext* browser_context = context_->GetBrowserContext(); | 
|  | DCHECK(browser_context); | 
|  | if (!browser_context->GetPermissionManager()) | 
|  | return; | 
|  |  | 
|  | GURL requesting_origin(origin.Serialize()); | 
|  | // If the embedding_origin is empty we'll use |origin| instead. | 
|  | GURL embedding_origin = context_->GetEmbeddingOrigin(); | 
|  | browser_context->GetPermissionManager()->ResetPermission( | 
|  | type, requesting_origin, | 
|  | embedding_origin.is_empty() ? requesting_origin : embedding_origin); | 
|  | } | 
|  |  | 
|  | }  // namespace content |