// 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 "components/permissions/permission_manager.h"

#include <algorithm>
#include <memory>
#include <utility>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "build/build_config.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/browser/permission_settings_info.h"
#include "components/content_settings/core/browser/permission_settings_registry.h"
#include "components/content_settings/core/common/content_settings_pattern.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/content_setting_permission_context_base.h"
#include "components/permissions/features.h"
#include "components/permissions/permission_decision.h"
#include "components/permissions/permission_request_id.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/permissions_client.h"
#include "components/permissions/request_type.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.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 "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
#include "url/origin.h"

using blink::PermissionType;
using SubscriptionId = content::PermissionController::SubscriptionId;

namespace permissions {
namespace {

GURL GetEmbeddingOrigin(content::RenderFrameHost* const render_frame_host,
                        const GURL& requesting_origin) {
  return PermissionsClient::Get()
      ->GetEmbeddingOriginOverride(requesting_origin, render_frame_host)
      .value_or(PermissionUtil::GetLastCommittedOriginAsURL(
          render_frame_host->GetMainFrame()));
}

PermissionSetting GetPermissionSettingForSubscription(
    ContentSettingsType content_settings_type,
    const content::PermissionResult& result) {
  const content_settings::PermissionSettingsInfo* info =
      content_settings::PermissionSettingsRegistry::GetInstance()->Get(
          content_settings_type);
  switch (result.status) {
    case PermissionStatus::GRANTED:
      CHECK(result.retrieved_permission_setting.has_value());
      return *result.retrieved_permission_setting;
    case PermissionStatus::ASK:
      return info->delegate().ToPermissionSetting(CONTENT_SETTING_ASK);
    case PermissionStatus::DENIED:
      return info->delegate().ToPermissionSetting(CONTENT_SETTING_BLOCK);
    default:
      NOTREACHED();
  }
}
}  // anonymous namespace

class PermissionManager::PendingRequest {
 public:
  PendingRequest(
      content::RenderFrameHost* render_frame_host,
      std::vector<blink::mojom::PermissionDescriptorPtr> permissions,
      base::OnceCallback<void(const std::vector<content::PermissionResult>&)>
          callback)
      : render_process_id_(render_frame_host->GetProcess()->GetDeprecatedID()),
        render_frame_id_(render_frame_host->GetRoutingID()),
        callback_(std::move(callback)),
        remaining_results_(permissions.size()),
        results_(permissions.size(),
                 content::PermissionResult(
                     PermissionStatus::DENIED,
                     content::PermissionStatusSource::UNSPECIFIED)),
        permissions_(std::move(permissions)) {}

  void SetPermissionResult(int permission_id,
                           content::PermissionResult decision) {
    DCHECK(!IsComplete());

    results_[permission_id] = decision;
    --remaining_results_;
  }

  bool IsComplete() const { return remaining_results_ == 0; }

  int render_process_id() const { return render_process_id_; }
  int render_frame_id() const { return render_frame_id_; }

  base::OnceCallback<void(const std::vector<content::PermissionResult>&)>
  TakeCallback() {
    return std::move(callback_);
  }

  const std::vector<blink::mojom::PermissionDescriptorPtr>& permissions()
      const {
    return permissions_;
  }

  std::vector<content::PermissionResult> results() const { return results_; }

 private:
  int render_process_id_;
  int render_frame_id_;
  base::OnceCallback<void(const std::vector<content::PermissionResult>&)>
      callback_;
  size_t remaining_results_;
  std::vector<content::PermissionResult> results_;
  std::vector<blink::mojom::PermissionDescriptorPtr> permissions_;
};

// Object to track the callback passed to
// PermissionContextBase::RequestPermission. The callback passed
// in will never be run when a permission prompt has been ignored, but it's
// important that we know when a prompt is ignored to clean up
// |pending_requests_| correctly. If the callback is destroyed without being
// run, the destructor here will cancel the request to clean up.
// |permission_manager| must outlive this object.
class PermissionManager::PermissionResponseCallback {
 public:
  PermissionResponseCallback(
      const base::WeakPtr<PermissionManager>& permission_manager,
      PendingRequestLocalId request_local_id,
      int permission_id)
      : permission_manager_(permission_manager),
        request_local_id_(request_local_id),
        permission_id_(permission_id),
        request_answered_(false) {}

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

  ~PermissionResponseCallback() {
    if (!request_answered_ && permission_manager_ &&
        permission_manager_->pending_requests_.Lookup(request_local_id_)) {
      permission_manager_->pending_requests_.Remove(request_local_id_);
    }
  }

  void OnPermissionsRequestResponse(
      content::PermissionResult permission_result) {
    if (!permission_manager_) {
      return;
    }
    request_answered_ = true;
    permission_manager_->OnPermissionsRequestResponse(
        request_local_id_, permission_id_, permission_result);
  }

 private:
  base::WeakPtr<PermissionManager> permission_manager_;
  PendingRequestLocalId request_local_id_;
  int permission_id_;
  bool request_answered_;
};

PermissionManager::PermissionManager(content::BrowserContext* browser_context,
                                     PermissionContextMap permission_contexts)
    : browser_context_(browser_context),
      permission_contexts_(std::move(permission_contexts)) {
  auto* autoblocker =
      permissions::PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
          browser_context_);
  if (autoblocker) {
    autoblocker->AddObserver(this);
  }
}

PermissionManager::~PermissionManager() {
  DCHECK(pending_requests_.IsEmpty());
}

void PermissionManager::Shutdown() {
  is_shutting_down_ = true;

  if (subscriptions()) {
    SetSubscriptions(nullptr);
  }

  content_settings_subscriptions_.Clear();

  for (const auto& type_to_count : subscription_type_counts_) {
    if (type_to_count.second > 0) {
      PermissionContextBase* context =
          GetPermissionContext(type_to_count.first);
      if (context != nullptr) {
        context->RemoveObserver(this);
      }
    }
  }
  subscription_type_counts_.clear();
  permission_contexts_.clear();

  auto* autoblocker =
      permissions::PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
          browser_context_);
  if (autoblocker) {
    autoblocker->RemoveObserver(this);
  }
}

void PermissionManager::OnEmbargoStarted(const GURL& origin,
                                         ContentSettingsType content_setting) {
  auto primary_pattern = ContentSettingsPattern::FromURLNoWildcard(origin);
  OnPermissionChanged(primary_pattern, ContentSettingsPattern::Wildcard(),
                      ContentSettingsTypeSet(content_setting));
}

PermissionContextBase* PermissionManager::GetPermissionContextForTesting(
    ContentSettingsType type) {
  return GetPermissionContext(type);
}

PermissionContextBase* PermissionManager::GetPermissionContext(
    ContentSettingsType type) {
  const auto& it = permission_contexts_.find(type);
  return it == permission_contexts_.end() ? nullptr : it->second.get();
}


void PermissionManager::RequestPermissionsInternal(
    content::RenderFrameHost* render_frame_host,
    const content::PermissionRequestDescription& request_description,
    base::OnceCallback<void(const std::vector<content::PermissionResult>&)>
        permission_status_callback) {
  std::vector<blink::mojom::PermissionDescriptorPtr> permissions;
  permissions.reserve(request_description.permissions.size());
  for (const auto& permission : request_description.permissions) {
    permissions.push_back(permission.Clone());
  }

  if (request_description.permissions.empty()) {
    std::move(permission_status_callback)
        .Run(std::vector<content::PermissionResult>());
    return;
  }

  auto request_local_id = request_local_id_generator_.GenerateNextId();
  pending_requests_.AddWithID(std::make_unique<PendingRequest>(
                                  render_frame_host, std::move(permissions),
                                  std::move(permission_status_callback)),
                              request_local_id);

  const PermissionRequestID request_id(render_frame_host, request_local_id);
  const GURL embedding_origin = GetEmbeddingOrigin(
      render_frame_host, request_description.requesting_origin);
  for (size_t i = 0; i < request_description.permissions.size(); ++i) {
    const ContentSettingsType permission =
        PermissionUtil::PermissionTypeToContentSettingsType(
            blink::PermissionDescriptorToPermissionType(
                request_description.permissions[i]));
    const GURL canonical_requesting_origin = PermissionUtil::GetCanonicalOrigin(
        permission, request_description.requesting_origin, embedding_origin);

    auto response_callback = std::make_unique<PermissionResponseCallback>(
        weak_factory_.GetWeakPtr(), request_local_id, i);
    PermissionContextBase* context = GetPermissionContext(permission);
    if (!context || PermissionUtil::IsPermissionBlockedInPartition(
                        permission, request_description.requesting_origin,
                        render_frame_host->GetProcess())) {
      response_callback->OnPermissionsRequestResponse(content::PermissionResult(
          PermissionStatus::DENIED,
          content::PermissionStatusSource::FEATURE_POLICY));
      continue;
    }

    context->RequestPermission(
        std::make_unique<PermissionRequestData>(
            request_id, request_description,
            canonical_requesting_origin.DeprecatedGetOriginAsURL(), GURL(), i),
        base::BindOnce(
            [](base::WeakPtr<PermissionManager> manager,
               ContentSettingsType permission,
               base::OnceCallback<void(content::PermissionResult)> callback,
               content::PermissionResult result) {
              if (!manager) {
                return;
              }

              if (auto* context = manager->GetPermissionContext(permission)) {
                context->MaybeOverridePermissionResultToReturn(result);
              }
              std::move(callback).Run(std::move(result));
            },
            weak_factory_.GetWeakPtr(), permission,
            base::BindOnce(
                &PermissionResponseCallback::OnPermissionsRequestResponse,
                std::move(response_callback))));
  }
}

void PermissionManager::ResetPermission(PermissionType permission,
                                        const GURL& requesting_origin,
                                        const GURL& embedding_origin) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  ContentSettingsType type =
      PermissionUtil::PermissionTypeToContentSettingsType(permission);
  PermissionContextBase* context = GetPermissionContext(type);
  if (!context)
    return;
  context->ResetPermission(PermissionUtil::GetCanonicalOrigin(
                               type, requesting_origin, embedding_origin),
                           embedding_origin.DeprecatedGetOriginAsURL());
}

PermissionManager::ContentSettingsTypeSubscription::
    ContentSettingsTypeSubscription() = default;
PermissionManager::ContentSettingsTypeSubscription::
    ~ContentSettingsTypeSubscription() = default;

SubscriptionId PermissionManager::SubscribeToContentSettingsTypeChange(
    ContentSettingsType content_settings_type,
    const GURL& requesting_origin,
    const GURL& embedding_origin,
    base::RepeatingCallback<void(const PermissionSetting&)> callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (is_shutting_down_) {
    return SubscriptionId();
  }

  PermissionContextBase* context = GetPermissionContext(content_settings_type);
  if (context == nullptr) {
    return SubscriptionId();
  }

  auto subscription = std::make_unique<ContentSettingsTypeSubscription>();
  subscription->content_settings_type = content_settings_type;
  subscription->requesting_origin = requesting_origin;
  subscription->embedding_origin = embedding_origin;
  subscription->callback = std::move(callback);

  auto permission_descriptor = content::PermissionDescriptorUtil::
      CreatePermissionDescriptorForPermissionType(
          PermissionUtil::ContentSettingsTypeToPermissionType(
              content_settings_type));

  content::PermissionResult result = GetPermissionStatusInternal(
      permission_descriptor,
      /*render_process_host=*/nullptr,
      /*render_frame_host=*/nullptr, requesting_origin, embedding_origin,
      /*should_include_device_status=*/false);
  subscription->last_setting =
      GetPermissionSettingForSubscription(content_settings_type, result);

  if (subscription_type_counts_[content_settings_type]++ == 0) {
    context->AddObserver(this);
  }

  SubscriptionId id = subscription_id_generator_.GenerateNextId();
  content_settings_subscriptions_.AddWithID(std::move(subscription), id);
  return id;
}

void PermissionManager::UnsubscribeFromContentSettingsTypeChange(
    SubscriptionId subscription_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (is_shutting_down_) {
    return;
  }

  ContentSettingsTypeSubscription* subscription =
      content_settings_subscriptions_.Lookup(subscription_id);
  if (!subscription) {
    return;
  }

  ContentSettingsType type = subscription->content_settings_type;
  auto type_count = subscription_type_counts_.find(type);
  CHECK(type_count != subscription_type_counts_.end());
  CHECK_GT(type_count->second, 0u);

  type_count->second--;
  if (type_count->second == 0) {
    PermissionContextBase* context = GetPermissionContext(type);
    if (context != nullptr) {
      context->RemoveObserver(this);
    }
  }

  content_settings_subscriptions_.Remove(subscription_id);
}

void PermissionManager::RequestPermissionsFromCurrentDocument(
    content::RenderFrameHost* render_frame_host,
    const content::PermissionRequestDescription& request_description,
    base::OnceCallback<void(const std::vector<content::PermissionResult>&)>
        permission_status_callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  RequestPermissionsInternal(render_frame_host, request_description,
                             std::move(permission_status_callback));
}

PermissionStatus PermissionManager::GetPermissionStatus(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    const GURL& requesting_origin,
    const GURL& embedding_origin) {
  // TODO(benwells): split this into two functions, GetPermissionStatus and
  // GetPermissionStatusForPermissionsAPI.
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  return GetPermissionStatusInternal(permission_descriptor,
                                     /*render_process_host=*/nullptr,
                                     /*render_frame_host=*/nullptr,
                                     requesting_origin, embedding_origin,
                                     /*should_include_device_status=*/false)
      .status;
}

content::PermissionResult
PermissionManager::GetPermissionResultForOriginWithoutContext(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    const url::Origin& requesting_origin,
    const url::Origin& embedding_origin) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  return GetPermissionStatusInternal(
      permission_descriptor,
      /*render_process_host=*/nullptr,
      /*render_frame_host=*/nullptr, requesting_origin.GetURL(),
      embedding_origin.GetURL(), /*should_include_device_status=*/false);
}

content::PermissionResult
PermissionManager::GetPermissionResultForCurrentDocument(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    content::RenderFrameHost* render_frame_host,
    bool should_include_device_status) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  const GURL requesting_origin =
      PermissionUtil::GetLastCommittedOriginAsURL(render_frame_host);
  const GURL embedding_origin =
      GetEmbeddingOrigin(render_frame_host, requesting_origin);

  return GetPermissionStatusInternal(
      permission_descriptor,
      /*render_process_host=*/nullptr, render_frame_host, requesting_origin,
      embedding_origin, should_include_device_status);
}

content::PermissionResult PermissionManager::GetPermissionResultForWorker(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    content::RenderProcessHost* render_process_host,
    const GURL& worker_origin) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  return GetPermissionStatusInternal(permission_descriptor, render_process_host,
                                     /*render_frame_host=*/nullptr,
                                     worker_origin, worker_origin,
                                     /*should_include_device_status=*/false);
}

content::PermissionResult
PermissionManager::GetPermissionResultForEmbeddedRequester(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    content::RenderFrameHost* render_frame_host,
    const url::Origin& requesting_origin) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  const GURL embedding_origin =
      GetEmbeddingOrigin(render_frame_host, requesting_origin.GetURL());

  return GetPermissionStatusInternal(
      permission_descriptor,
      /*render_process_host=*/nullptr, render_frame_host,
      requesting_origin.GetURL(), embedding_origin,
      /*should_include_device_status=*/false);
}

bool PermissionManager::IsPermissionOverridable(
    PermissionType permission,
    base::optional_ref<const url::Origin> requesting_origin,
    base::optional_ref<const url::Origin> embedding_origin) {
  ContentSettingsType type =
      PermissionUtil::PermissionTypeToContentSettingsTypeSafe(permission);
  PermissionContextBase* context = GetPermissionContext(type);

  if (!context || context->IsPermissionKillSwitchOn())
    return false;

  return !requesting_origin || !embedding_origin ||
         context->IsPermissionAvailableToOrigins(requesting_origin->GetURL(),
                                                 embedding_origin->GetURL());
}

void PermissionManager::OnPermissionStatusChangeSubscriptionAdded(
    SubscriptionId subscription_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (is_shutting_down_)
    return;
  if (!subscriptions() || subscriptions()->IsEmpty()) {
    return;
  }
  content::PermissionResultSubscription* subscription =
      subscriptions()->Lookup(subscription_id);
  if (!subscription) {
    return;
  }
  ContentSettingsType content_type =
      PermissionUtil::PermissionTypeToContentSettingsType(
          blink::PermissionDescriptorToPermissionType(
              subscription->permission_descriptor));
  auto& type_count = subscription_type_counts_[content_type];
  if (type_count == 0) {
    PermissionContextBase* context = GetPermissionContext(content_type);
    if (context == nullptr) {
      return;
    }
    context->AddObserver(this);
  }
  ++type_count;

  if (subscription->render_frame_id != -1) {
    subscription->embedding_origin = GetEmbeddingOrigin(
        content::RenderFrameHost::FromID(subscription->render_process_id,
                                         subscription->render_frame_id),
        subscription->requesting_origin);
    subscription->permission_result = GetPermissionStatusInternal(
        content::PermissionDescriptorUtil::
            CreatePermissionDescriptorForPermissionType(
                permissions::PermissionUtil::
                    ContentSettingsTypeToPermissionType(content_type)),
        /*render_process_host=*/nullptr,
        content::RenderFrameHost::FromID(subscription->render_process_id,
                                         subscription->render_frame_id),
        subscription->requesting_origin, subscription->embedding_origin,
        subscription->should_include_device_status);
  } else {
    subscription->permission_result = GetPermissionStatusInternal(
        content::PermissionDescriptorUtil::
            CreatePermissionDescriptorForPermissionType(
                permissions::PermissionUtil::
                    ContentSettingsTypeToPermissionType(content_type)),
        content::RenderProcessHost::FromID(subscription->render_process_id),
        /*render_frame_host=*/nullptr, subscription->requesting_origin,
        subscription->embedding_origin,
        subscription->should_include_device_status);
  }
  subscription->requesting_origin_delegation =
      PermissionUtil::GetCanonicalOrigin(content_type,
                                         subscription->requesting_origin,
                                         subscription->embedding_origin);
}

void PermissionManager::UnsubscribeFromPermissionResultChange(
    SubscriptionId subscription_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (is_shutting_down_)
    return;

  if (!subscriptions()) {
    return;
  }
  content::PermissionResultSubscription* subscription =
      subscriptions()->Lookup(subscription_id);
  if (!subscription)
    return;

  ContentSettingsType type =
      PermissionUtil::PermissionTypeToContentSettingsType(
          blink::PermissionDescriptorToPermissionType(
              subscription->permission_descriptor));
  auto type_count = subscription_type_counts_.find(type);
  CHECK(type_count != subscription_type_counts_.end());
  // type_count is zero only in the tests that we are directly calling
  // subscribing functions but is not subscribing to any real permission
  // context.
  PermissionContextBase* context = GetPermissionContext(type);
  if (type_count->second == 0) {
    if (context == nullptr) {
      return;
    }
  }
  CHECK_GT(type_count->second, size_t(0));
  type_count->second--;
  if (type_count->second == 0) {
    if (context != nullptr) {
      context->RemoveObserver(this);
    }
  }
}

std::optional<gfx::Rect> PermissionManager::GetExclusionAreaBoundsInScreen(
    content::WebContents* web_contents) const {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  auto* manager = PermissionRequestManager::FromWebContents(web_contents);
  return manager ? manager->GetPromptBubbleViewBoundsInScreen() : std::nullopt;
}

void PermissionManager::OnPermissionsRequestResponse(
    PendingRequestLocalId request_local_id,
    int permission_id,
    content::PermissionResult permission_result) {
  PendingRequest* pending_request = pending_requests_.Lookup(request_local_id);
  if (!pending_request)
    return;

  pending_request->SetPermissionResult(permission_id, permission_result);

  if (!pending_request->IsComplete()) {
    return;
  }

  pending_request->TakeCallback().Run(pending_request->results());
  pending_requests_.Remove(request_local_id);
}

void PermissionManager::OnPermissionChanged(
    const ContentSettingsPattern& primary_pattern,
    const ContentSettingsPattern& secondary_pattern,
    ContentSettingsTypeSet content_type_set) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  DCHECK(primary_pattern.IsValid());
  DCHECK(secondary_pattern.IsValid());

  std::vector<base::OnceClosure> callbacks;

  if (subscriptions()) {
    for (content::PermissionController::SubscriptionsMap::iterator iter(
             subscriptions());
         !iter.IsAtEnd(); iter.Advance()) {
      content::PermissionResultSubscription* subscription =
          iter.GetCurrentValue();
      if (!subscription) {
        continue;
      }
      if (!content_type_set.Contains(
              PermissionUtil::PermissionTypeToContentSettingsType(
                  blink::PermissionDescriptorToPermissionType(
                      subscription->permission_descriptor)))) {
        continue;
      }

      // The RFH may be null if the request is for a worker.
      content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
          subscription->render_process_id, subscription->render_frame_id);
      GURL embedding_origin;
      GURL requesting_origin_delegation =
          subscription->requesting_origin_delegation;
      if (rfh) {
        embedding_origin =
            GetEmbeddingOrigin(rfh, requesting_origin_delegation);
      } else {
        embedding_origin = requesting_origin_delegation;
      }

      if (!primary_pattern.Matches(requesting_origin_delegation) ||
          !secondary_pattern.Matches(embedding_origin)) {
        continue;
      }

      content::RenderProcessHost* rph =
          rfh ? nullptr
              : content::RenderProcessHost::FromID(
                    subscription->render_process_id);

      content::PermissionResult new_result = GetPermissionStatusInternal(
          subscription->permission_descriptor, rph, rfh,
          subscription->requesting_origin_delegation, embedding_origin,
          subscription->should_include_device_status);

      if (subscription->permission_result &&
          subscription->permission_result->status == new_result.status &&
          subscription->permission_result->retrieved_permission_setting ==
              new_result.retrieved_permission_setting) {
        continue;
      }

      subscription->permission_result = new_result;

      // Add the callback to |callbacks| which will be run after the loop to
      // prevent re-entrance issues.
      callbacks.push_back(base::BindOnce(subscription->callback, new_result,
                                         /*ignore_status_override=*/false));
    }
  }

  for (base::IDMap<std::unique_ptr<ContentSettingsTypeSubscription>,
                   SubscriptionId>::iterator
           iter(&content_settings_subscriptions_);
       !iter.IsAtEnd(); iter.Advance()) {
    ContentSettingsTypeSubscription* subscription = iter.GetCurrentValue();
    if (!subscription) {
      continue;
    }

    if (!content_type_set.Contains(subscription->content_settings_type)) {
      continue;
    }

    if (!primary_pattern.Matches(subscription->requesting_origin) ||
        !secondary_pattern.Matches(subscription->embedding_origin)) {
      continue;
    }

    auto permission_descriptor = content::PermissionDescriptorUtil::
        CreatePermissionDescriptorForPermissionType(
            PermissionUtil::ContentSettingsTypeToPermissionType(
                subscription->content_settings_type));

    content::PermissionResult result = GetPermissionStatusInternal(
        permission_descriptor,
        /*render_process_host=*/nullptr,
        /*render_frame_host=*/nullptr, subscription->requesting_origin,
        subscription->embedding_origin,
        /*should_include_device_status=*/false);

    // For GEOLOCATION, even if there are two permission types (GEOLOCATION and
    // GEOLOCATION_APPROXIMATE) corresponding to the content settings type, the
    // resolved `new_setting` must not depend on which of the two is used to
    // query. `ContentSettingsTypeToPermissionType` above resolves to
    // `GEOLOCATION` which is sufficient.
    PermissionSetting new_setting = GetPermissionSettingForSubscription(
        subscription->content_settings_type, result);

    if (subscription->last_setting == new_setting) {
      continue;
    }

    subscription->last_setting = new_setting;

    callbacks.push_back(base::BindOnce(subscription->callback, new_setting));
  }

  for (auto& callback : callbacks)
    std::move(callback).Run();
}

content::PermissionResult PermissionManager::GetPermissionStatusInternal(
    const blink::mojom::PermissionDescriptorPtr& permission_descriptor,
    content::RenderProcessHost* render_process_host,
    content::RenderFrameHost* render_frame_host,
    const GURL& requesting_origin,
    const GURL& embedding_origin,
    bool should_include_device_status) {
  DCHECK(!render_process_host || !render_frame_host);

  // TODO(crbug.com/40218610): Move this to PermissionContextBase.
  content::RenderProcessHost* rph =
      render_frame_host ? render_frame_host->GetProcess() : render_process_host;

  auto content_settings_type =
      PermissionUtil::PermissionTypeToContentSettingsType(
          blink::PermissionDescriptorToPermissionType(permission_descriptor));
  PermissionContextBase* context = GetPermissionContext(content_settings_type);

  if (!context || (rph && PermissionUtil::IsPermissionBlockedInPartition(
                              content_settings_type, requesting_origin, rph))) {
    return content::PermissionResult(
        PermissionStatus::DENIED, content::PermissionStatusSource::UNSPECIFIED);
  }

  GURL canonical_requesting_origin = PermissionUtil::GetCanonicalOrigin(
      content_settings_type, requesting_origin, embedding_origin);
  content::PermissionResult result = context->GetPermissionStatus(
      permission_descriptor, render_frame_host,
      canonical_requesting_origin.DeprecatedGetOriginAsURL(),
      embedding_origin.DeprecatedGetOriginAsURL());
  content::WebContents* const web_contents =
      content::WebContents::FromRenderFrameHost(render_frame_host);
  if (should_include_device_status || context->AlwaysIncludeDeviceStatus()) {
    result = context->UpdatePermissionStatusWithDeviceStatus(
        web_contents, result, requesting_origin, embedding_origin);
  } else {
    // Give the context an opportunity to still check the device status and
    // maybe notify observers.
    context->MaybeUpdateCachedHasDevicePermission(web_contents);
  }
  context->MaybeOverridePermissionResultToReturn(result);
  DCHECK(result.status == PermissionStatus::GRANTED ||
         result.status == PermissionStatus::ASK ||
         result.status == PermissionStatus::DENIED)
      << result.status;

  return result;
}

}  // namespace permissions
