Chromium Code Reviews| Index: android_webview/browser/aw_permission_manager.cc |
| diff --git a/android_webview/browser/aw_permission_manager.cc b/android_webview/browser/aw_permission_manager.cc |
| index a428250f940152f3d242bc52f2892e37674f6cf7..60c188440fa786cb9814a712f8e47ecc9a0ad7de 100644 |
| --- a/android_webview/browser/aw_permission_manager.cc |
| +++ b/android_webview/browser/aw_permission_manager.cc |
| @@ -18,8 +18,22 @@ |
| using blink::mojom::PermissionStatus; |
| using content::PermissionType; |
| +using RequestPermissionsCallback = |
| + base::Callback<void(const std::vector<PermissionStatus>&)>; |
| + |
| namespace android_webview { |
| +namespace { |
| + |
| +void PermissionRequestResponseCallbackWrapper( |
| + const base::Callback<void(PermissionStatus)>& callback, |
| + const std::vector<PermissionStatus>& vector) { |
| + DCHECK_EQ(vector.size(), 1ul); |
| + callback.Run(vector[0]); |
| +} |
| + |
| +} // namespace |
| + |
| class LastRequestResultCache { |
| public: |
| LastRequestResultCache() = default; |
| @@ -131,29 +145,82 @@ class LastRequestResultCache { |
| DISALLOW_COPY_AND_ASSIGN(LastRequestResultCache); |
| }; |
| -struct AwPermissionManager::PendingRequest { |
| +class AwPermissionManager::PendingRequest { |
| public: |
| - PendingRequest(PermissionType permission, |
| + PendingRequest(const std::vector<PermissionType> permissions, |
| GURL requesting_origin, |
| GURL embedding_origin, |
| content::RenderFrameHost* render_frame_host, |
| - const base::Callback<void(PermissionStatus)>& callback) |
| - : permission(permission), |
| - requesting_origin(requesting_origin), |
| - embedding_origin(embedding_origin), |
| - render_process_id(render_frame_host->GetProcess()->GetID()), |
| - render_frame_id(render_frame_host->GetRoutingID()), |
| - callback(callback) { |
| + const RequestPermissionsCallback callback) |
| + : permissions(permissions), |
| + requesting_origin(requesting_origin), |
| + embedding_origin(embedding_origin), |
| + render_process_id(render_frame_host->GetProcess()->GetID()), |
| + render_frame_id(render_frame_host->GetRoutingID()), |
| + callback(callback), |
| + results(permissions.size(), PermissionStatus::DENIED), |
| + cancelled_(false) { |
| + for (size_t i = 0; i < permissions.size(); ++i) |
| + permission_index_map_.insert(std::make_pair(permissions[i], i)); |
| } |
| ~PendingRequest() = default; |
| - PermissionType permission; |
| + void SetPermissionStatus(int index, PermissionStatus status) { |
| + DCHECK(!IsComplete()); |
| + |
| + results[index] = status; |
| + |
| + DCHECK(!IsComplete(permissions[index])); |
| + resolved_permissions_.insert(permissions[index]); |
| + } |
| + |
| + void SetPermissionStatus(PermissionType type, PermissionStatus status) { |
| + auto result = permission_index_map_.find(type); |
| + if (result == permission_index_map_.end()) { |
| + NOTREACHED(); |
| + return; |
| + } |
| + SetPermissionStatus(result->second, status); |
| + } |
| + |
| + PermissionStatus GetPermissionStatus(PermissionType type) { |
| + auto result = permission_index_map_.find(type); |
| + if (result == permission_index_map_.end()) { |
| + NOTREACHED(); |
| + return PermissionStatus::DENIED; |
| + } |
| + return results[result->second]; |
| + } |
| + |
| + bool HasPermissionType(PermissionType type) { |
| + return permission_index_map_.find(type) != permission_index_map_.end(); |
| + } |
| + |
| + bool IsComplete() const { |
| + return results.size() == resolved_permissions_.size(); |
| + } |
| + |
| + bool IsComplete(PermissionType type) const { |
| + return resolved_permissions_.count(type) != 0; |
| + } |
| + |
| + void Cancel() { cancelled_ = true; } |
| + |
| + bool IsCancelled() const { return cancelled_; } |
| + |
| + std::vector<PermissionType> permissions; |
| GURL requesting_origin; |
| GURL embedding_origin; |
| int render_process_id; |
| int render_frame_id; |
| - base::Callback<void(PermissionStatus)> callback; |
| + RequestPermissionsCallback callback; |
| + std::vector<PermissionStatus> results; |
| + |
| + private: |
| + std::map<PermissionType, size_t> permission_index_map_; |
| + std::set<PermissionType> resolved_permissions_; |
| + bool cancelled_; |
| }; |
| AwPermissionManager::AwPermissionManager() |
| @@ -163,6 +230,21 @@ AwPermissionManager::AwPermissionManager() |
| } |
| AwPermissionManager::~AwPermissionManager() { |
| + std::vector<int> request_ids; |
| + for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| + !it.IsAtEnd(); it.Advance()) { |
| + request_ids.push_back(it.GetCurrentKey()); |
| + } |
| + for (auto request_id : request_ids) { |
| + PendingRequest* request = pending_requests_.Lookup(request_id); |
| + if (!request) |
| + continue; |
| + const RequestPermissionsCallback callback = request->callback; |
| + std::vector<PermissionStatus> results = request->results; |
| + CancelPermissionRequest(request_id); |
| + callback.Run(results); |
| + } |
| + DCHECK(pending_requests_.IsEmpty()); |
| } |
| int AwPermissionManager::RequestPermission( |
| @@ -171,170 +253,178 @@ int AwPermissionManager::RequestPermission( |
| const GURL& requesting_origin, |
| bool user_gesture, |
| const base::Callback<void(PermissionStatus)>& callback) { |
| + return RequestPermissions( |
| + std::vector<PermissionType>(1, permission), render_frame_host, |
| + requesting_origin, user_gesture, |
| + base::Bind(&PermissionRequestResponseCallbackWrapper, callback)); |
| +} |
| + |
| +int AwPermissionManager::RequestPermissions( |
| + const std::vector<PermissionType>& permissions, |
| + content::RenderFrameHost* render_frame_host, |
| + const GURL& requesting_origin, |
| + bool user_gesture, |
| + const base::Callback<void(const std::vector<PermissionStatus>&)>& |
| + callback) { |
| + if (permissions.empty()) { |
| + callback.Run(std::vector<PermissionStatus>()); |
| + return kNoPendingOperation; |
| + } |
| + |
| int render_process_id = render_frame_host->GetProcess()->GetID(); |
| int render_frame_id = render_frame_host->GetRoutingID(); |
| AwBrowserPermissionRequestDelegate* delegate = |
| AwBrowserPermissionRequestDelegate::FromID(render_process_id, |
| render_frame_id); |
| if (!delegate) { |
| - DVLOG(0) << "Dropping permission request for " |
| - << static_cast<int>(permission); |
| - callback.Run(PermissionStatus::DENIED); |
| + DVLOG(0) << "Dropping permissions request"; |
| + callback.Run(std::vector<PermissionStatus>(permissions.size(), |
| + PermissionStatus::DENIED)); |
| return kNoPendingOperation; |
| } |
| - // Do not delegate any requests which are already pending. |
| - bool should_delegate_request = true; |
| - for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| - !it.IsAtEnd(); it.Advance()) { |
| - if (permission == it.GetCurrentValue()->permission) { |
| - should_delegate_request = false; |
| - break; |
| - } |
| - } |
| - |
| const GURL& embedding_origin = |
| content::WebContents::FromRenderFrameHost(render_frame_host) |
| ->GetLastCommittedURL().GetOrigin(); |
| - int request_id = kNoPendingOperation; |
| - switch (permission) { |
| - case PermissionType::GEOLOCATION: |
| - request_id = pending_requests_.Add(new PendingRequest( |
| - permission, requesting_origin, |
| - embedding_origin, render_frame_host, |
| - callback)); |
| - if (should_delegate_request) { |
| + PendingRequest* pending_request = |
| + new PendingRequest(permissions, requesting_origin, embedding_origin, |
| + render_frame_host, callback); |
| + std::vector<bool> should_delegate_requests = |
| + std::vector<bool>(permissions.size()); |
| + for (size_t i = 0; i < permissions.size(); ++i) { |
| + bool should_delegate_request = true; |
| + for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| + !it.IsAtEnd(); it.Advance()) { |
| + if (it.GetCurrentValue()->HasPermissionType(permissions[i])) { |
| + // TODO(toyoshim): Shall we check to match requesting_origin too? |
| + should_delegate_request = false; |
| + if (it.GetCurrentValue()->IsComplete(permissions[i])) { |
| + pending_request->SetPermissionStatus( |
| + i, it.GetCurrentValue()->GetPermissionStatus(permissions[i])); |
| + } |
| + break; |
| + } |
| + } |
| + should_delegate_requests[i] = should_delegate_request; |
| + } |
| + |
| + int request_id = pending_requests_.Add(pending_request); |
| + |
| + for (size_t i = 0; i < permissions.size(); ++i) { |
| + if (!should_delegate_requests[i]) |
| + continue; |
| + |
| + switch (permissions[i]) { |
| + case PermissionType::GEOLOCATION: |
| delegate->RequestGeolocationPermission( |
| requesting_origin, |
| - base::Bind(&OnRequestResponse, |
| - weak_ptr_factory_.GetWeakPtr(), request_id, |
| - callback)); |
| - } |
| - break; |
| - case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| - request_id = pending_requests_.Add(new PendingRequest( |
| - permission, requesting_origin, |
| - embedding_origin, render_frame_host, |
| - callback)); |
| - if (should_delegate_request) { |
| + base::Bind(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(), |
| + request_id, i)); |
| + break; |
| + case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| delegate->RequestProtectedMediaIdentifierPermission( |
| requesting_origin, |
| - base::Bind(&OnRequestResponse, |
| - weak_ptr_factory_.GetWeakPtr(), request_id, |
| - callback)); |
| - } |
| - break; |
| - case PermissionType::MIDI_SYSEX: |
| - request_id = pending_requests_.Add(new PendingRequest( |
| - permission, requesting_origin, |
| - embedding_origin, render_frame_host, |
| - callback)); |
| - if (should_delegate_request) { |
| + base::Bind(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(), |
| + request_id, i)); |
| + break; |
| + case PermissionType::MIDI_SYSEX: |
| delegate->RequestMIDISysexPermission( |
| requesting_origin, |
| - base::Bind(&OnRequestResponse, |
| - weak_ptr_factory_.GetWeakPtr(), request_id, |
| - callback)); |
| - } |
| - break; |
| - case PermissionType::AUDIO_CAPTURE: |
| - case PermissionType::VIDEO_CAPTURE: |
| - case PermissionType::NOTIFICATIONS: |
| - case PermissionType::PUSH_MESSAGING: |
| - case PermissionType::DURABLE_STORAGE: |
| - case PermissionType::BACKGROUND_SYNC: |
| - NOTIMPLEMENTED() << "RequestPermission is not implemented for " |
| - << static_cast<int>(permission); |
| - callback.Run(PermissionStatus::DENIED); |
| - break; |
| - case PermissionType::MIDI: |
| - callback.Run(PermissionStatus::GRANTED); |
| - break; |
| - case PermissionType::NUM: |
| - NOTREACHED() << "PermissionType::NUM was not expected here."; |
| - callback.Run(PermissionStatus::DENIED); |
| - break; |
| + base::Bind(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(), |
| + request_id, i)); |
| + break; |
| + case PermissionType::AUDIO_CAPTURE: |
| + case PermissionType::VIDEO_CAPTURE: |
| + case PermissionType::NOTIFICATIONS: |
| + case PermissionType::PUSH_MESSAGING: |
| + case PermissionType::DURABLE_STORAGE: |
| + case PermissionType::BACKGROUND_SYNC: |
| + NOTIMPLEMENTED() << "RequestPermissions is not implemented for " |
| + << static_cast<int>(permissions[i]); |
| + pending_request->SetPermissionStatus(i, PermissionStatus::DENIED); |
| + break; |
| + case PermissionType::MIDI: |
| + pending_request->SetPermissionStatus(i, PermissionStatus::GRANTED); |
| + break; |
| + case PermissionType::NUM: |
| + NOTREACHED() << "PermissionType::NUM was not expected here."; |
| + pending_request->SetPermissionStatus(i, PermissionStatus::DENIED); |
| + break; |
| + } |
| } |
| - return request_id; |
| -} |
| - |
| -int AwPermissionManager::RequestPermissions( |
| - const std::vector<PermissionType>& permissions, |
| - content::RenderFrameHost* render_frame_host, |
| - const GURL& requesting_origin, |
| - bool user_gesture, |
| - const base::Callback<void( |
| - const std::vector<PermissionStatus>&)>& callback) { |
| - NOTIMPLEMENTED() << "RequestPermissions has not been implemented in WebView"; |
| - std::vector<PermissionStatus> result(permissions.size()); |
| - const GURL& embedding_origin = |
| - content::WebContents::FromRenderFrameHost(render_frame_host) |
| - ->GetLastCommittedURL().GetOrigin(); |
| + // If delegate resolve the permission synchronously, all requests could be |
| + // already resolved here. |
| + if (!pending_requests_.Lookup(request_id)) |
| + return kNoPendingOperation; |
| - for (PermissionType type : permissions) { |
| - result.push_back(GetPermissionStatus( |
| - type, requesting_origin, embedding_origin)); |
| + // If requests are resolved without calling delegate functions, e.g. |
| + // PermissionType::MIDI is permitted within the previous for-loop, all |
| + // requests could be already resolved, but still in the |pending_requests_| |
| + // without invoking the callback. |
| + if (pending_request->IsComplete()) { |
| + std::vector<PermissionStatus> results = pending_request->results; |
| + pending_requests_.Remove(request_id); |
| + callback.Run(results); |
| + return kNoPendingOperation; |
| } |
| - callback.Run(result); |
| - return kNoPendingOperation; |
| + return request_id; |
| } |
| // static |
| void AwPermissionManager::OnRequestResponse( |
| const base::WeakPtr<AwPermissionManager>& manager, |
| int request_id, |
| - const base::Callback<void(PermissionStatus)>& callback, |
| + int request_index, |
| bool allowed) { |
| + // All delegate functions should be cancelled when the manager runs |
| + // destructor. Therefore |manager| should be always valid here. |
| + DCHECK(manager); |
| + |
| PermissionStatus status = |
| allowed ? PermissionStatus::GRANTED : PermissionStatus::DENIED; |
| - if (manager.get()) { |
| - PendingRequest* pending_request = |
| - manager->pending_requests_.Lookup(request_id); |
| - |
| - for (PendingRequestsMap::Iterator<PendingRequest> it( |
| - &manager->pending_requests_); |
| - !it.IsAtEnd(); it.Advance()) { |
| - if (pending_request->permission == it.GetCurrentValue()->permission && |
| - it.GetCurrentKey() != request_id) { |
| - it.GetCurrentValue()->callback.Run(status); |
| - manager->pending_requests_.Remove(it.GetCurrentKey()); |
| + PendingRequest* pending_request = |
| + manager->pending_requests_.Lookup(request_id); |
| + PermissionType permission_type = pending_request->permissions[request_index]; |
| + |
| + manager->result_cache_->SetResult(permission_type, |
| + pending_request->requesting_origin, |
| + pending_request->embedding_origin, status); |
| + |
| + std::vector<int> complete_request_ids; |
| + std::vector<std::pair<const RequestPermissionsCallback, |
| + std::vector<PermissionStatus>>> |
| + complete_request_pairs; |
| + for (PendingRequestsMap::Iterator<PendingRequest> it( |
| + &manager->pending_requests_); |
| + !it.IsAtEnd(); it.Advance()) { |
| + it.GetCurrentValue()->SetPermissionStatus(permission_type, status); |
| + if (it.GetCurrentValue()->IsComplete()) { |
| + complete_request_ids.push_back(it.GetCurrentKey()); |
| + if (!it.GetCurrentValue()->IsCancelled()) { |
| + complete_request_pairs.push_back(std::make_pair( |
| + it.GetCurrentValue()->callback, it.GetCurrentValue()->results)); |
| } |
| } |
| - |
| - manager->result_cache_->SetResult( |
| - pending_request->permission, |
| - pending_request->requesting_origin, |
| - pending_request->embedding_origin, |
| - status); |
| - manager->pending_requests_.Remove(request_id); |
| } |
| - callback.Run(status); |
| + for (auto id : complete_request_ids) |
| + manager->pending_requests_.Remove(id); |
| + for (auto pair : complete_request_pairs) |
| + pair.first.Run(pair.second); |
| } |
| void AwPermissionManager::CancelPermissionRequest(int request_id) { |
| PendingRequest* pending_request = pending_requests_.Lookup(request_id); |
| - if (!pending_request) |
| + if (!pending_request || pending_request->IsCancelled()) |
| return; |
| + pending_request->Cancel(); |
| - content::RenderFrameHost* render_frame_host = |
| - content::RenderFrameHost::FromID(pending_request->render_process_id, |
| - pending_request->render_frame_id); |
| - content::WebContents* web_contents = |
| - content::WebContents::FromRenderFrameHost(render_frame_host); |
| - DCHECK(web_contents); |
| - |
| - // The caller is canceling (presumably) the most recent request. Assuming the |
| - // request did not complete, the user did not respond to the requset. |
| - // Thus, assume we do not know the result. |
| - const GURL& embedding_origin = web_contents |
| - ->GetLastCommittedURL().GetOrigin(); |
| - result_cache_->ClearResult( |
| - pending_request->permission, |
| - pending_request->requesting_origin, |
| - embedding_origin); |
| + const GURL& embedding_origin = pending_request->embedding_origin; |
| + const GURL& requesting_origin = pending_request->requesting_origin; |
| + for (auto permission : pending_request->permissions) |
| + result_cache_->ClearResult(permission, requesting_origin, embedding_origin); |
| AwBrowserPermissionRequestDelegate* delegate = |
| AwBrowserPermissionRequestDelegate::FromID( |
| @@ -345,37 +435,66 @@ void AwPermissionManager::CancelPermissionRequest(int request_id) { |
| return; |
| } |
| - switch (pending_request->permission) { |
| - case PermissionType::GEOLOCATION: |
| - delegate->CancelGeolocationPermissionRequests( |
| - pending_request->requesting_origin); |
| - break; |
| - case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| - delegate->CancelProtectedMediaIdentifierPermissionRequests( |
| - pending_request->requesting_origin); |
| - break; |
| - case PermissionType::MIDI_SYSEX: |
| - delegate->CancelMIDISysexPermissionRequests( |
| - pending_request->requesting_origin); |
| - break; |
| - case PermissionType::NOTIFICATIONS: |
| - case PermissionType::PUSH_MESSAGING: |
| - case PermissionType::DURABLE_STORAGE: |
| - case PermissionType::AUDIO_CAPTURE: |
| - case PermissionType::VIDEO_CAPTURE: |
| - case PermissionType::BACKGROUND_SYNC: |
| - NOTIMPLEMENTED() << "CancelPermission not implemented for " |
| - << static_cast<int>(pending_request->permission); |
| - break; |
| - case PermissionType::MIDI: |
| - // There is nothing to cancel so this is simply ignored. |
| - break; |
| - case PermissionType::NUM: |
| - NOTREACHED() << "PermissionType::NUM was not expected here."; |
| - break; |
| + for (auto permission : pending_request->permissions) { |
| + // If the permission was already resolved, we do not need to cancel it. |
| + if (pending_request->IsComplete(permission)) |
| + continue; |
| + |
| + // If another pending_request waits for the same permission being resolved, |
| + // we should not cancel the request. |
| + bool should_not_cancel_ = false; |
| + for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_); |
| + !it.IsAtEnd(); it.Advance()) { |
| + if (it.GetCurrentValue() == pending_request) |
| + continue; |
| + // We need to check if |permission| was resolved for the current request |
| + // in addition to the |pending_request|. This is because |permission| |
| + // might be resolved for the current request before |pending_request| was |
| + // issued. |
| + if (it.GetCurrentValue()->HasPermissionType(permission) && |
|
Tobias Sargeant
2016/07/14 10:03:40
checking the requesting origin when making the req
Takashi Toyoshima
2016/07/14 12:40:49
Done.
|
| + !it.GetCurrentValue()->IsComplete(permission)) { |
| + should_not_cancel_ = true; |
| + break; |
| + } |
| + } |
| + if (should_not_cancel_) |
| + continue; |
| + |
| + switch (permission) { |
| + case PermissionType::GEOLOCATION: |
| + delegate->CancelGeolocationPermissionRequests(requesting_origin); |
| + break; |
| + case PermissionType::PROTECTED_MEDIA_IDENTIFIER: |
| + delegate->CancelProtectedMediaIdentifierPermissionRequests( |
| + requesting_origin); |
| + break; |
| + case PermissionType::MIDI_SYSEX: |
| + delegate->CancelMIDISysexPermissionRequests(requesting_origin); |
| + break; |
| + case PermissionType::NOTIFICATIONS: |
| + case PermissionType::PUSH_MESSAGING: |
| + case PermissionType::DURABLE_STORAGE: |
| + case PermissionType::AUDIO_CAPTURE: |
| + case PermissionType::VIDEO_CAPTURE: |
| + case PermissionType::BACKGROUND_SYNC: |
| + NOTIMPLEMENTED() << "CancelPermission not implemented for " |
| + << static_cast<int>(permission); |
| + break; |
| + case PermissionType::MIDI: |
| + // There is nothing to cancel so this is simply ignored. |
| + break; |
| + case PermissionType::NUM: |
| + NOTREACHED() << "PermissionType::NUM was not expected here."; |
| + break; |
| + } |
| + pending_request->SetPermissionStatus(permission, PermissionStatus::DENIED); |
| } |
| - pending_requests_.Remove(request_id); |
| + // If there are still active requests, we should not remove request_id here, |
| + // but just do not invoke a relevant callback when the request is resolved in |
| + // OnRequestResponse(). |
| + if (pending_request->IsComplete()) |
| + pending_requests_.Remove(request_id); |
| } |
| void AwPermissionManager::ResetPermission(PermissionType permission, |