chromium/android_webview/browser/aw_permission_manager.cc

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "android_webview/browser/aw_permission_manager.h"

#include <memory>
#include <string>
#include <unordered_map>
#include <utility>

#include "android_webview/browser/aw_app_defined_websites.h"
#include "android_webview/browser/aw_browser_permission_request_delegate.h"
#include "android_webview/browser/aw_contents.h"
#include "android_webview/browser/aw_context_permissions_delegate.h"
#include "android_webview/browser/aw_settings.h"
#include "android_webview/common/aw_features.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/task/thread_pool.h"
#include "components/permissions/permission_manager.h"
#include "components/permissions/permission_util.h"
#include "content/public/browser/child_process_security_policy.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_result.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"

using blink::PermissionType;
using blink::mojom::PermissionStatus;

using RequestPermissionsCallback =
    base::OnceCallback<void(const std::vector<PermissionStatus>&)>;

namespace android_webview {

class LastRequestResultCache {
 public:
  LastRequestResultCache() = default;

  LastRequestResultCache(const LastRequestResultCache&) = delete;
  LastRequestResultCache& operator=(const LastRequestResultCache&) = delete;

  void SetResult(PermissionType permission,
                 const GURL& requesting_origin,
                 const GURL& embedding_origin,
                 PermissionStatus status) {
    DCHECK(status == PermissionStatus::GRANTED ||
           status == PermissionStatus::DENIED);

    // TODO(ddorwin): We should be denying empty origins at a higher level.
    if (requesting_origin.is_empty() || embedding_origin.is_empty()) {
      DLOG(WARNING) << "Not caching result because of empty origin.";
      return;
    }

    if (!requesting_origin.is_valid()) {
      NOTREACHED() << requesting_origin.possibly_invalid_spec();
    }
    if (!embedding_origin.is_valid()) {
      NOTREACHED() << embedding_origin.possibly_invalid_spec();
    }

    switch (permission) {
      case PermissionType::PROTECTED_MEDIA_IDENTIFIER:
      case PermissionType::STORAGE_ACCESS_GRANT:
      case PermissionType::TOP_LEVEL_STORAGE_ACCESS:
        break;
      // Other permissions are not cached.
      default:
        return;
    }

    std::string key = GetCacheKey(requesting_origin, embedding_origin);
    if (key.empty()) {
      NOTREACHED();
    }
    pmi_result_cache_[key] = status;
  }

  PermissionStatus GetResult(PermissionType permission,
                             const GURL& requesting_origin,
                             const GURL& embedding_origin) const {
    // TODO(ddorwin): We should be denying empty origins at a higher level.
    if (requesting_origin.is_empty() || embedding_origin.is_empty()) {
      return PermissionStatus::ASK;
    }

    DCHECK(requesting_origin.is_valid())
        << requesting_origin.possibly_invalid_spec();
    DCHECK(embedding_origin.is_valid())
        << embedding_origin.possibly_invalid_spec();

    switch (permission) {
      case PermissionType::PROTECTED_MEDIA_IDENTIFIER:
      case PermissionType::STORAGE_ACCESS_GRANT:
      case PermissionType::TOP_LEVEL_STORAGE_ACCESS:
        break;
      // Other permissions are not cached.
      default:
        NOTREACHED()
            << "Results are only cached for PROTECTED_MEDIA_IDENTIFIER AND SAA";
    }

    std::string key = GetCacheKey(requesting_origin, embedding_origin);
    StatusMap::const_iterator it = pmi_result_cache_.find(key);
    if (it == pmi_result_cache_.end()) {
      DLOG(WARNING) << "GetResult() called for uncached origins: " << key;
      return PermissionStatus::ASK;
    }

    DCHECK(!key.empty());
    return it->second;
  }

  void ClearResult(PermissionType permission,
                   const GURL& requesting_origin,
                   const GURL& embedding_origin) {
    // TODO(ddorwin): We should be denying empty origins at a higher level.
    if (requesting_origin.is_empty() || embedding_origin.is_empty()) {
      return;
    }

    DCHECK(requesting_origin.is_valid())
        << requesting_origin.possibly_invalid_spec();
    DCHECK(embedding_origin.is_valid())
        << embedding_origin.possibly_invalid_spec();


    if (permission != PermissionType::PROTECTED_MEDIA_IDENTIFIER) {
      // Other permissions are not cached, so nothing to clear.
      return;
    }

    std::string key = GetCacheKey(requesting_origin, embedding_origin);
    pmi_result_cache_.erase(key);
  }

 private:
  // Returns a concatenation of the origins to be used as the index.
  // Returns the empty string if either origin is invalid or empty.
  static std::string GetCacheKey(const GURL& requesting_origin,
                                 const GURL& embedding_origin) {
    const std::string& requesting = requesting_origin.spec();
    const std::string& embedding = embedding_origin.spec();
    if (requesting.empty() || embedding.empty())
      return std::string();
    return requesting + "," + embedding;
  }

  using StatusMap = std::unordered_map<std::string, PermissionStatus>;
  StatusMap pmi_result_cache_;
};

class AwPermissionManager::PendingRequest {
 public:
  PendingRequest(const std::vector<PermissionType> permissions,
                 GURL requesting_origin,
                 GURL embedding_origin,
                 int render_process_id,
                 int render_frame_id,
                 RequestPermissionsCallback callback)
      : permissions(permissions),
        requesting_origin(requesting_origin),
        embedding_origin(embedding_origin),
        render_process_id(render_process_id),
        render_frame_id(render_frame_id),
        callback(std::move(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;

  void SetPermissionStatus(PermissionType type, PermissionStatus status) {
    auto result = permission_index_map_.find(type);
    if (result == permission_index_map_.end()) {
      NOTREACHED();
    }
    DCHECK(!IsCompleted());
    results[result->second] = status;
    if (base::FeatureList::IsEnabled(blink::features::kBlockMidiByDefault)) {
      if (type == PermissionType::MIDI && status == PermissionStatus::GRANTED) {
        content::ChildProcessSecurityPolicy::GetInstance()
            ->GrantSendMidiMessage(render_process_id);
      }
    }
    if (type == PermissionType::MIDI_SYSEX &&
        status == PermissionStatus::GRANTED) {
      content::ChildProcessSecurityPolicy::GetInstance()
          ->GrantSendMidiSysExMessage(render_process_id);
    }
    resolved_permissions_.insert(type);
  }

  PermissionStatus GetPermissionStatus(PermissionType type) {
    auto result = permission_index_map_.find(type);
    if (result == permission_index_map_.end()) {
      NOTREACHED();
    }
    return results[result->second];
  }

  bool HasPermissionType(PermissionType type) {
    return base::Contains(permission_index_map_, type);
  }

  bool IsCompleted() const {
    return results.size() == resolved_permissions_.size();
  }

  bool IsCompleted(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;
  RequestPermissionsCallback callback;
  std::vector<PermissionStatus> results;

 private:
  std::map<PermissionType, size_t> permission_index_map_;
  std::set<PermissionType> resolved_permissions_;
  bool cancelled_;
};

// Regarding the saa_cache_ size: 99% of WebView site visits fall
// into this count (based on Android.WebView.SitesVisitedWeekly) in a week so we
// will cache those for revisits but not going above it to avoid using up too
// much memory for any heavy case. This will cache in memory so every app reload
// will require a new request. This will also be duplicated across profiles
// which may be a useful property in the future so we aren't going to design
// around that.
AwPermissionManager::AwPermissionManager(
    const AwContextPermissionsDelegate& context_delegate)
    : context_delegate_(context_delegate),
      result_cache_(new LastRequestResultCache),
      saa_cache_(10) {}

AwPermissionManager::~AwPermissionManager() {
  CancelPermissionRequests();
}

void AwPermissionManager::RequestPermissions(
    content::RenderFrameHost* render_frame_host,
    const content::PermissionRequestDescription& request_description,
    base::OnceCallback<void(const std::vector<PermissionStatus>&)> callback) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  auto const& permissions = request_description.permissions;
  if (permissions.empty()) {
    std::move(callback).Run(std::vector<PermissionStatus>());
    return;
  }

  const GURL& embedding_origin = LastCommittedMainOrigin(render_frame_host);
  const GURL& requesting_origin = request_description.requesting_origin;

  auto pending_request = std::make_unique<PendingRequest>(
      permissions, requesting_origin, embedding_origin,
      GetRenderProcessID(render_frame_host),
      GetRenderFrameID(render_frame_host), std::move(callback));
  std::vector<bool> should_delegate_requests =
      std::vector<bool>(permissions.size(), true);
  for (size_t i = 0; i < permissions.size(); ++i) {
    for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_);
         !it.IsAtEnd(); it.Advance()) {
      if (it.GetCurrentValue()->HasPermissionType(permissions[i]) &&
          it.GetCurrentValue()->requesting_origin == requesting_origin) {
        if (it.GetCurrentValue()->IsCompleted(permissions[i])) {
          pending_request->SetPermissionStatus(
              permissions[i],
              it.GetCurrentValue()->GetPermissionStatus(permissions[i]));
        }
        should_delegate_requests[i] = false;
        break;
      }
    }
  }

  // Keep copy of pointer for performing further operations after ownership is
  // transferred to pending_requests_
  PendingRequest* pending_request_raw = pending_request.get();
  const int request_id = pending_requests_.Add(std::move(pending_request));

  AwBrowserPermissionRequestDelegate* delegate =
      GetDelegate(pending_request_raw->render_process_id,
                  pending_request_raw->render_frame_id);

  for (size_t i = 0; i < permissions.size(); ++i) {
    if (!should_delegate_requests[i])
      continue;

    if (!delegate) {
      DVLOG(0) << "Dropping permissions request for "
               << static_cast<int>(permissions[i]);
      pending_request_raw->SetPermissionStatus(permissions[i],
                                               PermissionStatus::DENIED);
      continue;
    }

    switch (permissions[i]) {
      case PermissionType::GEOLOCATION:
        delegate->RequestGeolocationPermission(
            pending_request_raw->requesting_origin,
            base::BindOnce(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(),
                           request_id, permissions[i]));
        break;
      case PermissionType::PROTECTED_MEDIA_IDENTIFIER:
        delegate->RequestProtectedMediaIdentifierPermission(
            pending_request_raw->requesting_origin,
            base::BindOnce(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(),
                           request_id, permissions[i]));
        break;
      case PermissionType::MIDI_SYSEX:
        delegate->RequestMIDISysexPermission(
            pending_request_raw->requesting_origin,
            base::BindOnce(&OnRequestResponse, weak_ptr_factory_.GetWeakPtr(),
                           request_id, permissions[i]));
        break;
      case PermissionType::CLIPBOARD_SANITIZED_WRITE:
        // This is the permission for writing vetted data (such as plain text or
        // sanitized images) using the async clipboard API. Chrome automatically
        // grants access with a user gesture, and alternatively queries for
        // gesture-less access with a popup bubble. For now, just grant based on
        // user gesture.
        // Reading from the clipboard or writing custom data is represented with
        // the CLIPBOARD_READ_WRITE permission, and that requires an explicit
        // user approval, which is not implemented yet. See crbug.com/1271620
        pending_request_raw->SetPermissionStatus(
            permissions[i], request_description.user_gesture
                                ? PermissionStatus::GRANTED
                                : PermissionStatus::DENIED);
        break;
      case PermissionType::AUDIO_CAPTURE:
      case PermissionType::VIDEO_CAPTURE:
      case PermissionType::NOTIFICATIONS:
      case PermissionType::DURABLE_STORAGE:
      case PermissionType::BACKGROUND_SYNC:
      case PermissionType::ACCESSIBILITY_EVENTS:
      case PermissionType::CLIPBOARD_READ_WRITE:
      case PermissionType::PAYMENT_HANDLER:
      case PermissionType::BACKGROUND_FETCH:
      case PermissionType::IDLE_DETECTION:
      case PermissionType::PERIODIC_BACKGROUND_SYNC:
      case PermissionType::NFC:
      case PermissionType::VR:
      case PermissionType::AR:
      case PermissionType::HAND_TRACKING:
      case PermissionType::CAMERA_PAN_TILT_ZOOM:
      case PermissionType::WINDOW_MANAGEMENT:
      case PermissionType::LOCAL_FONTS:
      case PermissionType::DISPLAY_CAPTURE:
      case PermissionType::CAPTURED_SURFACE_CONTROL:
      case PermissionType::SMART_CARD:
      case PermissionType::WEB_PRINTING:
      case PermissionType::SPEAKER_SELECTION:
      case PermissionType::KEYBOARD_LOCK:
      case PermissionType::POINTER_LOCK:
      case PermissionType::AUTOMATIC_FULLSCREEN:
      case PermissionType::WEB_APP_INSTALLATION:
        NOTIMPLEMENTED() << "RequestPermissions is not implemented for "
                         << static_cast<int>(permissions[i]);
        pending_request_raw->SetPermissionStatus(permissions[i],
                                                 PermissionStatus::DENIED);
        break;
      case PermissionType::STORAGE_ACCESS_GRANT:
      case PermissionType::TOP_LEVEL_STORAGE_ACCESS: {
        const url::Origin& outer_origin =
            render_frame_host->GetOutermostMainFrame()
                ->GetLastCommittedOrigin();

        auto cached_value = saa_cache_->Get(outer_origin.Serialize());
        if (cached_value != saa_cache_->end()) {
          auto is_granted = cached_value->second ? PermissionStatus::GRANTED
                                                 : PermissionStatus::DENIED;
          pending_request_raw->SetPermissionStatus(permissions[i], is_granted);
          break;
        }

        auto on_saa_response =
            base::BindOnce(&CacheAutoSAA, weak_ptr_factory_.GetWeakPtr(),
                           outer_origin)
                .Then(base::BindOnce(&OnRequestResponse,
                                     weak_ptr_factory_.GetWeakPtr(), request_id,
                                     permissions[i]));

        delegate->RequestStorageAccess(outer_origin,
                                       std::move(on_saa_response));
        break;
      }
      case PermissionType::MIDI:
      case PermissionType::SENSORS:
      case PermissionType::WAKE_LOCK_SCREEN:
        // PermissionType::SENSORS requests are always granted so that access
        // to device motion and device orientation data (and underlying
        // sensors) works in the WebView. SensorProviderImpl::GetSensor()
        // filters requests for other types of sensors.
        pending_request_raw->SetPermissionStatus(permissions[i],
                                                 PermissionStatus::GRANTED);
        break;
      case PermissionType::WAKE_LOCK_SYSTEM:
        pending_request_raw->SetPermissionStatus(permissions[i],
                                                 PermissionStatus::DENIED);
        break;
      case PermissionType::NUM:
        NOTREACHED() << "PermissionType::NUM was not expected here.";
    }
  }

  // If delegate resolve the permission synchronously, all requests could be
  // already resolved here.
  if (!pending_requests_.Lookup(request_id))
    return;

  // 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_raw->IsCompleted()) {
    std::vector<PermissionStatus> results = pending_request_raw->results;
    RequestPermissionsCallback completed_callback =
        std::move(pending_request_raw->callback);
    pending_requests_.Remove(request_id);
    std::move(completed_callback).Run(results);
  }
}

// static
bool AwPermissionManager::CacheAutoSAA(
    const base::WeakPtr<AwPermissionManager>& manager,
    const url::Origin& origin,
    bool allowed) {
  // All delegate functions should be cancelled when the manager runs
  // destructor. Therefore |manager| should be always valid here.
  CHECK(manager);
  DCHECK_CALLED_ON_VALID_SEQUENCE(manager->sequence_checker_);

  DVLOG(1) << "Caching auto granted SAA result " << allowed << " for origin "
           << origin;

  manager->saa_cache_->Put({
      origin.Serialize(),
      allowed,
  });

  return allowed;
}

// static
void AwPermissionManager::OnRequestResponse(
    const base::WeakPtr<AwPermissionManager>& manager,
    int request_id,
    PermissionType permission,
    bool allowed) {
  // All delegate functions should be cancelled when the manager runs
  // destructor. Therefore |manager| should be always valid here.
  CHECK(manager);

  PermissionStatus status =
      allowed ? PermissionStatus::GRANTED : PermissionStatus::DENIED;
  PendingRequest* pending_request =
      manager->pending_requests_.Lookup(request_id);

  manager->result_cache_->SetResult(permission,
                                    pending_request->requesting_origin,
                                    pending_request->embedding_origin, status);

  std::vector<int> complete_request_ids;
  std::vector<
      std::pair<RequestPermissionsCallback, std::vector<PermissionStatus>>>
      complete_request_pairs;
  for (PendingRequestsMap::Iterator<PendingRequest> it(
           &manager->pending_requests_);
       !it.IsAtEnd(); it.Advance()) {
    if (!it.GetCurrentValue()->HasPermissionType(permission) ||
        it.GetCurrentValue()->requesting_origin !=
            pending_request->requesting_origin) {
      continue;
    }
    it.GetCurrentValue()->SetPermissionStatus(permission, status);
    if (it.GetCurrentValue()->IsCompleted()) {
      complete_request_ids.push_back(it.GetCurrentKey());
      if (!it.GetCurrentValue()->IsCancelled()) {
        complete_request_pairs.emplace_back(
            std::move(it.GetCurrentValue()->callback),
            std::move(it.GetCurrentValue()->results));
      }
    }
  }
  for (auto id : complete_request_ids)
    manager->pending_requests_.Remove(id);
  for (auto& pair : complete_request_pairs)
    std::move(pair.first).Run(pair.second);
}

void AwPermissionManager::ResetPermission(PermissionType permission,
                                          const GURL& requesting_origin,
                                          const GURL& embedding_origin) {
  result_cache_->ClearResult(permission, requesting_origin, embedding_origin);
}

void AwPermissionManager::RequestPermissionsFromCurrentDocument(
    content::RenderFrameHost* render_frame_host,
    const content::PermissionRequestDescription& request_description,
    base::OnceCallback<void(const std::vector<blink::mojom::PermissionStatus>&)>
        callback) {
  RequestPermissions(render_frame_host, request_description,
                     std::move(callback));
}

PermissionStatus AwPermissionManager::GetPermissionStatus(
    PermissionType permission,
    const GURL& requesting_origin,
    const GURL& embedding_origin) {
  return GetPermissionStatusInternal(permission, requesting_origin,
                                     embedding_origin,
                                     /*web_contents=*/nullptr);
}

PermissionStatus AwPermissionManager::GetPermissionStatusInternal(
    PermissionType permission,
    const GURL& requesting_origin,
    const GURL& embedding_origin,
    content::WebContents* web_contents) {
  switch (permission) {
    // Setting results is called outside the Permissions API only for these
    // permissions.
    case blink::PermissionType::STORAGE_ACCESS_GRANT:
    case blink::PermissionType::TOP_LEVEL_STORAGE_ACCESS: {
      if (!base::FeatureList::IsEnabled(features::kWebViewAutoSAA)) {
        return PermissionStatus::DENIED;
      }
      return result_cache_->GetResult(permission, requesting_origin,
                                      embedding_origin);
    }
    case blink::PermissionType::PROTECTED_MEDIA_IDENTIFIER:
      return result_cache_->GetResult(permission, requesting_origin,
                                      embedding_origin);

    case blink::PermissionType::GEOLOCATION:
      return GetGeolocationPermission(requesting_origin, web_contents);

    case blink::PermissionType::MIDI:
    case blink::PermissionType::SENSORS:
      // These permissions are auto-granted by WebView.
      return PermissionStatus::GRANTED;

    case blink::PermissionType::MIDI_SYSEX:
    case blink::PermissionType::AUDIO_CAPTURE:
    case blink::PermissionType::VIDEO_CAPTURE:
      // These permissions are always forwarded to the app to handle.
      return PermissionStatus::ASK;
    case blink::PermissionType::CLIPBOARD_SANITIZED_WRITE:
      // This permission depends on user_gesture, so should always ask.
      return PermissionStatus::ASK;

    case blink::PermissionType::ACCESSIBILITY_EVENTS:
    case blink::PermissionType::AR:
    case blink::PermissionType::AUTOMATIC_FULLSCREEN:
    case blink::PermissionType::BACKGROUND_FETCH:
    case blink::PermissionType::BACKGROUND_SYNC:
    case blink::PermissionType::CAMERA_PAN_TILT_ZOOM:
    case blink::PermissionType::CAPTURED_SURFACE_CONTROL:
    case blink::PermissionType::CLIPBOARD_READ_WRITE:
    case blink::PermissionType::DISPLAY_CAPTURE:
    case blink::PermissionType::DURABLE_STORAGE:
    case blink::PermissionType::HAND_TRACKING:
    case blink::PermissionType::IDLE_DETECTION:
    case blink::PermissionType::KEYBOARD_LOCK:
    case blink::PermissionType::LOCAL_FONTS:
    case blink::PermissionType::NFC:
    case blink::PermissionType::NOTIFICATIONS:
    case blink::PermissionType::NUM:
    case blink::PermissionType::PAYMENT_HANDLER:
    case blink::PermissionType::PERIODIC_BACKGROUND_SYNC:
    case blink::PermissionType::POINTER_LOCK:
    case blink::PermissionType::SMART_CARD:
    case blink::PermissionType::SPEAKER_SELECTION:
    case blink::PermissionType::VR:
    case blink::PermissionType::WAKE_LOCK_SCREEN:
    case blink::PermissionType::WAKE_LOCK_SYSTEM:
    case blink::PermissionType::WEB_APP_INSTALLATION:
    case blink::PermissionType::WEB_PRINTING:
    case blink::PermissionType::WINDOW_MANAGEMENT:
      return PermissionStatus::DENIED;
  }
  NOTREACHED() << "Unhandled permission type: " << static_cast<int>(permission);
}

PermissionStatus AwPermissionManager::GetGeolocationPermission(
    const GURL& requesting_origin,
    content::WebContents* web_contents) {
  if (!web_contents) {
    // If we don't have a web_contents, we can't determine if we have
    // permission.
    return PermissionStatus::ASK;
  }

  AwSettings* settings = AwSettings::FromWebContents(web_contents);
  if (!settings->geolocation_enabled()) {
    return PermissionStatus::DENIED;
  }
  AwContents* aw_contents = AwContents::FromWebContents(web_contents);
  if (!aw_contents->UseLegacyGeolocationPermissionAPI()) {
    // The new geolocation API does not have a cache for permission decisions,
    // so if that's in use, we will need to ask the app.
    return PermissionStatus::ASK;
  }
  return context_delegate_->GetGeolocationPermission(requesting_origin);
}

content::PermissionResult
AwPermissionManager::GetPermissionResultForOriginWithoutContext(
    blink::PermissionType permission,
    const url::Origin& requesting_origin,
    const url::Origin& embedding_origin) {
  blink::mojom::PermissionStatus status = GetPermissionStatus(
      permission, requesting_origin.GetURL(), embedding_origin.GetURL());

  return content::PermissionResult(
      status, content::PermissionStatusSource::UNSPECIFIED);
}

PermissionStatus AwPermissionManager::GetPermissionStatusForCurrentDocument(
    PermissionType permission,
    content::RenderFrameHost* render_frame_host,
    bool should_include_device_status) {
  content::WebContents* web_contents =
      content::WebContents::FromRenderFrameHost(render_frame_host);
  return GetPermissionStatusInternal(
      permission,
      permissions::PermissionUtil::GetLastCommittedOriginAsURL(
          render_frame_host),
      permissions::PermissionUtil::GetLastCommittedOriginAsURL(
          render_frame_host->GetMainFrame()),
      web_contents);
}

PermissionStatus AwPermissionManager::GetPermissionStatusForWorker(
    PermissionType permission,
    content::RenderProcessHost* render_process_host,
    const GURL& worker_origin) {
  return GetPermissionStatus(permission, worker_origin, worker_origin);
}

PermissionStatus AwPermissionManager::GetPermissionStatusForEmbeddedRequester(
    blink::PermissionType permission,
    content::RenderFrameHost* render_frame_host,
    const url::Origin& requesting_origin) {
  return GetPermissionStatusInternal(
      permission, requesting_origin.GetURL(),
      permissions::PermissionUtil::GetLastCommittedOriginAsURL(
          render_frame_host->GetMainFrame()),
      content::WebContents::FromRenderFrameHost(render_frame_host));
}

void AwPermissionManager::CancelPermissionRequest(int request_id) {
  PendingRequest* pending_request = pending_requests_.Lookup(request_id);
  if (!pending_request || pending_request->IsCancelled())
    return;
  pending_request->Cancel();

  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 = GetDelegate(
      pending_request->render_process_id, pending_request->render_frame_id);

  for (auto permission : pending_request->permissions) {
    // If the permission was already resolved, we do not need to cancel it.
    if (pending_request->IsCompleted(permission))
      continue;

    // If another pending_request waits for the same permission being resolved,
    // we should not cancel the delegate's request.
    bool should_not_cancel_ = false;
    for (PendingRequestsMap::Iterator<PendingRequest> it(&pending_requests_);
         !it.IsAtEnd(); it.Advance()) {
      if (it.GetCurrentValue() != pending_request &&
          it.GetCurrentValue()->HasPermissionType(permission) &&
          it.GetCurrentValue()->requesting_origin == requesting_origin &&
          !it.GetCurrentValue()->IsCompleted(permission)) {
        should_not_cancel_ = true;
        break;
      }
    }
    if (should_not_cancel_)
      continue;

    switch (permission) {
      case PermissionType::GEOLOCATION:
        if (delegate)
          delegate->CancelGeolocationPermissionRequests(requesting_origin);
        break;
      case PermissionType::PROTECTED_MEDIA_IDENTIFIER:
        if (delegate)
          delegate->CancelProtectedMediaIdentifierPermissionRequests(
              requesting_origin);
        break;
      case PermissionType::MIDI_SYSEX:
        if (delegate)
          delegate->CancelMIDISysexPermissionRequests(requesting_origin);
        break;
      case PermissionType::NOTIFICATIONS:
      case PermissionType::DURABLE_STORAGE:
      case PermissionType::AUDIO_CAPTURE:
      case PermissionType::VIDEO_CAPTURE:
      case PermissionType::BACKGROUND_SYNC:
      case PermissionType::ACCESSIBILITY_EVENTS:
      case PermissionType::CLIPBOARD_READ_WRITE:
      case PermissionType::CLIPBOARD_SANITIZED_WRITE:
      case PermissionType::PAYMENT_HANDLER:
      case PermissionType::BACKGROUND_FETCH:
      case PermissionType::IDLE_DETECTION:
      case PermissionType::PERIODIC_BACKGROUND_SYNC:
      case PermissionType::NFC:
      case PermissionType::HAND_TRACKING:
      case PermissionType::VR:
      case PermissionType::AR:
      case PermissionType::STORAGE_ACCESS_GRANT:
      case PermissionType::TOP_LEVEL_STORAGE_ACCESS:
      case PermissionType::CAMERA_PAN_TILT_ZOOM:
      case PermissionType::WINDOW_MANAGEMENT:
      case PermissionType::LOCAL_FONTS:
      case PermissionType::DISPLAY_CAPTURE:
      case PermissionType::CAPTURED_SURFACE_CONTROL:
      case PermissionType::SMART_CARD:
      case PermissionType::WEB_PRINTING:
      case PermissionType::SPEAKER_SELECTION:
      case PermissionType::KEYBOARD_LOCK:
      case PermissionType::POINTER_LOCK:
      case PermissionType::AUTOMATIC_FULLSCREEN:
      case PermissionType::WEB_APP_INSTALLATION:
        NOTIMPLEMENTED() << "CancelPermission not implemented for "
                         << static_cast<int>(permission);
        break;
      case PermissionType::MIDI:
      case PermissionType::SENSORS:
      case PermissionType::WAKE_LOCK_SCREEN:
      case PermissionType::WAKE_LOCK_SYSTEM:
        // There is nothing to cancel so this is simply ignored.
        break;
      case PermissionType::NUM:
        NOTREACHED() << "PermissionType::NUM was not expected here.";
    }
    pending_request->SetPermissionStatus(permission, PermissionStatus::DENIED);
  }

  // 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->IsCompleted())
    pending_requests_.Remove(request_id);
}

void AwPermissionManager::CancelPermissionRequests() {
  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)
    CancelPermissionRequest(request_id);
  DCHECK(pending_requests_.IsEmpty());
}

void AwPermissionManager::SetOriginCanReadEnumerateDevicesAudioLabels(
    const url::Origin& origin,
    bool audio) {
  auto it = enumerate_devices_labels_cache_.find(origin);
  if (it == enumerate_devices_labels_cache_.end()) {
    enumerate_devices_labels_cache_[origin] = std::make_pair(audio, false);
  } else {
    it->second.first = audio;
  }
}

void AwPermissionManager::SetOriginCanReadEnumerateDevicesVideoLabels(
    const url::Origin& origin,
    bool video) {
  auto it = enumerate_devices_labels_cache_.find(origin);
  if (it == enumerate_devices_labels_cache_.end())
    enumerate_devices_labels_cache_[origin] = std::make_pair(false, video);
  else
    it->second.second = video;
}

bool AwPermissionManager::ShouldShowEnumerateDevicesAudioLabels(
    const url::Origin& origin) {
  auto it = enumerate_devices_labels_cache_.find(origin);
  if (it == enumerate_devices_labels_cache_.end())
    return false;
  return it->second.first;
}

bool AwPermissionManager::ShouldShowEnumerateDevicesVideoLabels(
    const url::Origin& origin) {
  auto it = enumerate_devices_labels_cache_.find(origin);
  if (it == enumerate_devices_labels_cache_.end())
    return false;
  return it->second.second;
}

void AwPermissionManager::ClearEnumerateDevicesCachedPermission(
    const url::Origin& origin,
    bool remove_audio,
    bool remove_video) {
  auto it = enumerate_devices_labels_cache_.find(origin);
  if (it == enumerate_devices_labels_cache_.end())
    return;
  else if (remove_audio && remove_video) {
    enumerate_devices_labels_cache_.erase(origin);
  } else if (remove_audio) {
    it->second.first = false;
  } else if (remove_video) {
    it->second.second = false;
  }
}

int AwPermissionManager::GetRenderProcessID(
    content::RenderFrameHost* render_frame_host) {
  return render_frame_host->GetProcess()->GetID();
}

int AwPermissionManager::GetRenderFrameID(
    content::RenderFrameHost* render_frame_host) {
  return render_frame_host->GetRoutingID();
}

GURL AwPermissionManager::LastCommittedMainOrigin(
    content::RenderFrameHost* render_frame_host) {
  return permissions::PermissionUtil::GetLastCommittedOriginAsURL(
      render_frame_host->GetMainFrame());
}

AwBrowserPermissionRequestDelegate* AwPermissionManager::GetDelegate(
    int render_process_id,
    int render_frame_id) {
  return AwBrowserPermissionRequestDelegate::FromID(render_process_id,
                                                    render_frame_id);
}

}  // namespace android_webview