// Copyright 2014 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_request_manager.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <variant>

#include "base/auto_reset.h"
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/notreached.h"
#include "base/observer_list.h"
#include "base/rand_util.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "components/back_forward_cache/back_forward_cache_disable.h"
#include "components/content_settings/browser/page_specific_content_settings.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/permission_settings_registry.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/constants.h"
#include "components/permissions/embedded_permission_prompt_flow_model.h"
#include "components/permissions/features.h"
#include "components/permissions/origin_keyed_permission_action_service.h"
#include "components/permissions/permission_actions_history.h"
#include "components/permissions/permission_decision_auto_blocker.h"
#include "components/permissions/permission_prompt.h"
#include "components/permissions/permission_request.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/permissions_client.h"
#include "components/permissions/prediction_service/permission_ui_selector.h"
#include "components/permissions/request_type.h"
#include "components/permissions/resolvers/permission_prompt_options.h"
#include "components/permissions/switches.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/back_forward_cache.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/disallow_activation_reason.h"
#include "content/public/browser/navigation_handle.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 "ui/base/window_open_disposition_utils.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "url/gurl.h"
#include "url/origin.h"

#if BUILDFLAG(IS_ANDROID)
#include "components/permissions/android/android_permission_util.h"
#endif

namespace permissions {

const char kAbusiveNotificationRequestsEnforcementMessage[] =
    "Chrome is blocking notification permission requests on this site because "
    "the site tends to show permission requests that mislead, trick, or force "
    "users into allowing notifications. You should fix the issues as soon as "
    "possible and submit your site for another review. Learn more at "
    "https://support.google.com/webtools/answer/9799048.";

const char kAbusiveNotificationRequestsWarningMessage[] =
    "Chrome might start blocking notification permission requests on this site "
    "in the future because the site tends to show permission requests that "
    "mislead, trick, or force users into allowing notifications. You should "
    "fix the issues as soon as possible and submit your site for another "
    "review. Learn more at https://support.google.com/webtools/answer/9799048.";

constexpr char kAbusiveNotificationContentEnforcementMessage[] =
    "Chrome is blocking notification permission requests on this site because "
    "the site tends to show notifications with content that mislead or trick "
    "users. You should fix the issues as soon as possible and submit your site "
    "for another review. Learn more at "
    "https://support.google.com/webtools/answer/9799048";

constexpr char kAbusiveNotificationContentWarningMessage[] =
    "Chrome might start blocking notification permission requests on this site "
    "in the future because the site tends to show notifications with content "
    "that mislead or trick users. You should fix the issues as soon as "
    "possible and submit your site for another review. Learn more at "
    "https://support.google.com/webtools/answer/9799048";

constexpr char kDisruptiveNotificationBehaviorEnforcementMessage[] =
    "Chrome is blocking notification permission requests on this site because "
    "the site exhibits behaviors that may be disruptive to users.";

const char kGestureGatedNotificationMessage[] =
    "The Notification permission request was suppressed and shown as a quiet "
    "prompt because it was requested without a user gesture. Users are more "
    "likely to grant permissions when requested in context. See "
    "https://crbug.com/479151408 for more details.";

const char kGestureGatedGeolocationMessage[] =
    "The Geolocation permission request was suppressed and shown as a quiet "
    "prompt because it was requested without a user gesture. Users are more "
    "likely to grant permissions when requested in context. See "
    "https://crbug.com/479151408 for more details.";

namespace {

// In case of multiple permission requests that use chip UI, a newly added
// request will preempt the currently showing request, which is put back to the
// queue, and will be shown later. To reduce user annoyance, if a quiet chip
// permission prompt was displayed longer than `kQuietChipIgnoreTimeout`, we
// consider it as shown long enough and it will not be shown again after it is
// preempted.
// TODO(crbug.com/40186690): If a user switched tabs, do not include that time
// as "shown".
bool ShouldShowQuietRequestAgainIfPreempted(
    std::optional<base::Time> request_display_start_time) {
  if (request_display_start_time->is_null()) {
    return true;
  }

  static constexpr base::TimeDelta kQuietChipIgnoreTimeout = base::Seconds(8.5);
  return base::Time::Now() - request_display_start_time.value() <
         kQuietChipIgnoreTimeout;
}

bool IsMediaRequest(RequestType type) {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  if (type == RequestType::kCameraPanTiltZoom) {
    return true;
  }
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  return type == RequestType::kMicStream || type == RequestType::kCameraStream;
}

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
bool IsExclusiveAccessRequest(RequestType type) {
  return type == RequestType::kPointerLock ||
         type == RequestType::kKeyboardLock;
}
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)

bool ShouldGroupRequests(PermissionRequest* a, PermissionRequest* b) {
  if (a->requesting_origin() != b->requesting_origin()) {
    return false;
  }
  // Group if both requests are of the same category.
  if (IsMediaRequest(a->request_type()) && IsMediaRequest(b->request_type())) {
    return true;
  }
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  if (IsExclusiveAccessRequest(a->request_type()) &&
      IsExclusiveAccessRequest(b->request_type())) {
    return true;
  }
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  return false;
}

bool RequestExistsExactlyOnce(
    PermissionRequest* request,
    const PermissionRequestQueue& request_queue,
    const std::vector<std::unique_ptr<PermissionRequest>>& requests) {
  return request_queue.Contains(request) !=
         std::ranges::any_of(requests, [request](const auto& current_request) {
           return current_request.get() == request;
         });
}

tabs::TabInterface* GetTabInterface(content::WebContents* web_contents) {
#if BUILDFLAG(IS_ANDROID)
  return nullptr;
#else
  return tabs::TabInterface::MaybeGetFromContents(web_contents);
#endif  // BUILDFLAG(IS_ANDROID)
}

}  // namespace

// PermissionRequestManager ----------------------------------------------------

bool PermissionRequestManager::PermissionRequestSource::
    IsSourceFrameInactiveAndDisallowActivation() const {
  content::RenderFrameHost* rfh =
      content::RenderFrameHost::FromID(requesting_frame_id);
  return !rfh ||
         rfh->IsInactiveAndDisallowActivation(
             content::DisallowActivationReasonId::kPermissionRequestSource);
}

PermissionRequestManager::~PermissionRequestManager() {
  DCHECK(!IsRequestInProgress());
  DCHECK(duplicate_requests_.empty());
  DCHECK(pending_permission_requests_.IsEmpty());

  RecordPostPromptSessionDuration();

  for (Observer& observer : observer_list_) {
    observer.OnPermissionRequestManagerDestructed();
  }

  tab_subscriptions_.clear();
}

void PermissionRequestManager::AddRequest(
    content::RenderFrameHost* source_frame,
    std::unique_ptr<PermissionRequest> request) {
  DCHECK(source_frame);
  DCHECK_EQ(content::WebContents::FromRenderFrameHost(source_frame),
            web_contents());

  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kDenyPermissionPrompts)) {
    request->PermissionDenied();
    return;
  }

  if (display::Screen::Get()->IsHeadless()) {
    request->PermissionDenied();
    return;
  }

  if (source_frame->IsInactiveAndDisallowActivation(
          content::DisallowActivationReasonId::kPermissionAddRequest)) {
    request->Cancelled();
    return;
  }

  if (source_frame->IsNestedWithinFencedFrame()) {
    request->Cancelled();
    return;
  }

#if BUILDFLAG(IS_ANDROID)
  if (!base::FeatureList::IsEnabled(
          features::kReturnDeniedForNotificationsWhenNoAppLevelSettings) &&
      request->GetContentSettingsType() == ContentSettingsType::NOTIFICATIONS) {
    bool app_level_settings_allow_site_notifications =
        enabled_app_level_notification_permission_for_testing_.has_value()
            ? enabled_app_level_notification_permission_for_testing_.value()
            : DoesAppLevelSettingsAllowSiteNotifications();
    base::UmaHistogramBoolean(
        "Permissions.Prompt.Notifications.EnabledAppLevel",
        app_level_settings_allow_site_notifications);

    if (!app_level_settings_allow_site_notifications) {
      // Automatically cancel site Notification requests when Chrome is not
      // able to send notifications in an app level.
      request->Cancelled();
      return;
    }
  }
#endif  // BUILDFLAG(IS_ANDROID)

  if (is_notification_prompt_cooldown_active_ &&
      request->GetContentSettingsType() == ContentSettingsType::NOTIFICATIONS) {
    // Short-circuit by canceling rather than denying to avoid creating a large
    // number of content setting exceptions on Desktop / disabled notification
    // channels on Android.
    request->Cancelled();
    return;
  }

  if (!web_contents_supports_permission_requests_) {
    request->Cancelled();
    return;
  }

  // TODO(tsergeant): change the UMA to no longer mention bubble.
  base::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));

  // TODO(gbillock): is there a race between an early request on a
  // newly-navigated page and the to-be-cleaned-up requests on the previous
  // page? We should maybe listen to DidStartNavigationToPendingEntry (and
  // any other renderer-side nav initiations?). Double-check this for
  // correct behavior on interstitials -- we probably want to basically queue
  // any request for which GetVisibleURL != GetLastCommittedURL.
  CHECK(source_frame->GetMainFrame()->IsInPrimaryMainFrame());
  const GURL main_frame_origin =
      PermissionUtil::GetLastCommittedOriginAsURL(source_frame->GetMainFrame());
  bool is_main_frame =
      url::IsSameOriginWith(main_frame_origin, request->requesting_origin());

  const std::optional<PermissionAction> should_auto_approve_request =
      PermissionsClient::Get()->GetAutoApprovalStatus(
          web_contents()->GetBrowserContext(), request->requesting_origin());

  if (should_auto_approve_request) {
    PromptOptions prompt_options =
        request->GetContentSettingsType() ==
                ContentSettingsType::GEOLOCATION_WITH_OPTIONS
            // If a geolocation request should be auto-approved, we always grant
            // precise location.
            ? PromptOptions(GeolocationPromptOptions{
                  .selected_accuracy = GeolocationAccuracy::kPrecise})
            : std::monostate();
    if (should_auto_approve_request == PermissionAction::GRANTED) {
      request->PermissionGranted(prompt_options, /*is_one_time=*/false);
    } else if (should_auto_approve_request == PermissionAction::GRANTED_ONCE) {
      request->PermissionGranted(prompt_options, /*is_one_time=*/true);
    }
    return;
  }

  // Don't re-add an existing request or one with a duplicate text request.
  if (auto* existing_request = GetExistingRequest(request.get())) {
    // |request| is a duplicate. Add it to |duplicate_requests_| unless it's the
    // same object as |existing_request| or an existing duplicate.
    auto iter = FindDuplicateRequestList(existing_request);
    if (iter == duplicate_requests_.end()) {
      std::list<std::unique_ptr<PermissionRequest>> list;
      list.push_back(std::move(request));
      duplicate_requests_.push_back(std::move(list));
      return;
    }

    iter->push_back(std::move(request));
    return;
  }

  if (is_main_frame) {
    if (IsRequestInProgress()) {
      base::RecordAction(
          base::UserMetricsAction("PermissionBubbleRequestQueued"));
    }
  } else {
    base::RecordAction(
        base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
  }

  request->set_requesting_frame_id(source_frame->GetGlobalId());

  // Snapshot the source_id so that if the request is implicitly ignored because
  // the frame is navigated, we still record the correct source_id.
  request->set_ukm_source_id(source_frame->GetPageUkmSourceId());

  QueueRequest(source_frame, std::move(request));

  if (!IsRequestInProgress()) {
    ScheduleDequeueRequestIfNeeded();
    return;
  }

  ReprioritizeCurrentRequestIfNeeded();
}

bool PermissionRequestManager::ReprioritizeCurrentRequestIfNeeded() {
  // Use `ShouldCurrentRequestUsePermissionElementSecondaryUI` to also include
  // allowlisted surfaces.
  if (!IsRequestInProgress() ||
      PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI(
          this, web_contents()) ||
      !can_preempt_current_request_) {
    return true;
  }

  // Pop out all invalid requests in front of the queue.
  while (!pending_permission_requests_.IsEmpty() &&
         !HasActiveSourceFrameOrDisallowActivationOtherwise(
             *pending_permission_requests_.Peek())) {
    auto request = pending_permission_requests_.Pop();
    FinalizeAndCancelRequest(*request);
  }

  if (pending_permission_requests_.IsEmpty()) {
    return true;
  }

  auto current_request_fate = CurrentRequestFate::kKeepCurrent;

  if (PermissionUtil::DoesPlatformSupportChip()) {
    if (ShouldCurrentRequestUseQuietUI() &&
        !ShouldShowQuietRequestAgainIfPreempted(
            current_request_first_display_time_)) {
      current_request_fate = CurrentRequestFate::kFinalize;
    } else {
      // Preempt current request if it is a quiet UI request.
      if (ShouldCurrentRequestUseQuietUI()) {
        current_request_fate = CurrentRequestFate::kPreempt;
      } else {
        // Here we also try to prioritise the requests. If there's a valid high
        // priority request (high acceptance rate request) in the pending queue,
        // preempt the current request. The valid high priority request, if
        // there's any, is always the front of the queue.
        if (!pending_permission_requests_.IsEmpty() &&
            !PermissionUtil::IsLowPriorityPermissionRequest(
                pending_permission_requests_.Peek())) {
          current_request_fate = CurrentRequestFate::kPreempt;
        }
      }
    }
  } else if (ShouldCurrentRequestUseQuietUI()) {
    // If we're displaying a quiet permission request, ignore it in favor of a
    // new permission request.
    current_request_fate = CurrentRequestFate::kFinalize;
  }

  if (current_request_fate == CurrentRequestFate::kKeepCurrent &&
      !pending_permission_requests_.IsEmpty() &&
      pending_permission_requests_.Peek()
          ->IsEmbeddedPermissionElementInitiated()) {
    current_request_fate = CurrentRequestFate::kPreempt;
  }

  switch (current_request_fate) {
    case CurrentRequestFate::kKeepCurrent:
      return true;
    case CurrentRequestFate::kPreempt: {
      CHECK(!pending_permission_requests_.IsEmpty());
      // Consider a case of infinite loop here (eg: 2 low priority requests can
      // preempt each other, causing a loop). We only preempt the current
      // request if the next candidate has just been added to pending queue but
      // not validated yet.
      if (std::ranges::any_of(
              validated_requests_,
              [&](const raw_ref<PermissionRequest> element) -> bool {
                return element == *pending_permission_requests_.Peek();
              })) {
        return true;
      }

      auto next = pending_permission_requests_.Pop();
      PreemptAndRequeueCurrentRequest();
      pending_permission_requests_.PushFront(std::move(next));
      ScheduleDequeueRequestIfNeeded();
      return false;
    }
    case CurrentRequestFate::kFinalize:
      // FinalizeCurrentRequests() will call ScheduleDequeueRequestIfNeeded on
      // its own.
      CurrentRequestsDecided(PermissionAction::IGNORED,
                             /*prompt_options=*/std::monostate());
      return false;
  }

  return true;
}

bool PermissionRequestManager::
    HasActiveSourceFrameOrDisallowActivationOtherwise(
        const PermissionRequest& request) const {
  const auto iter = request_sources_map_.find(request);
  if (iter != request_sources_map_.end()) {
    return !iter->second.IsSourceFrameInactiveAndDisallowActivation();
  }
  return false;
}

void PermissionRequestManager::FinalizeAndCancelRequest(
    PermissionRequest& request) {
  if (request_sources_map_.erase(base::raw_ref(request)) > 0) {
    std::erase(validated_requests_, request);
  }
  request.Cancelled();
}

void PermissionRequestManager::QueueRequest(
    content::RenderFrameHost* source_frame,
    std::unique_ptr<PermissionRequest> request) {
  request_sources_map_.emplace(
      *request, PermissionRequestSource({source_frame->GetGlobalId()}));
  pending_permission_requests_.Push(std::move(request));
}

void PermissionRequestManager::PreemptAndRequeueCurrentRequest() {
  ResetViewStateForCurrentRequest();
  for (auto& current_request : requests_) {
    pending_permission_requests_.PushFront(std::move(current_request));
  }

  // Because the order of the requests is changed, we should not preignore it.
  preignore_timer_.Stop();

  requests_.clear();
}

void PermissionRequestManager::UpdateAnchor() {
  if (view_) {
    // When the prompt's anchor is being updated, the prompt view can be
    // recreated for the new browser. Because of that, ignore prompt callbacks
    // while doing that.
    base::AutoReset<bool> ignore(&ignore_callbacks_from_prompt_, true);
    if (!view_->UpdateAnchor()) {
      RecreateView();
    }
  }
}

void PermissionRequestManager::DidStartNavigation(
    content::NavigationHandle* navigation_handle) {
  for (Observer& observer : observer_list_) {
    observer.OnNavigation(navigation_handle);
  }

  if (!navigation_handle->IsInPrimaryMainFrame() ||
      navigation_handle->IsSameDocument()) {
    return;
  }

  RecordPostPromptSessionDuration();

  // Cooldown lasts until the next user-initiated navigation, which is defined
  // as either a renderer-initiated navigation with a user gesture, or a
  // browser-initiated navigation.
  //
  // TODO(crbug.com/40622940): This check has to be done at DidStartNavigation
  // time, the HasUserGesture state is lost by the time the navigation
  // commits.
  if (!navigation_handle->IsRendererInitiated() ||
      navigation_handle->HasUserGesture()) {
    is_notification_prompt_cooldown_active_ = false;
  }
}

void PermissionRequestManager::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  if (!navigation_handle->IsInPrimaryMainFrame() ||
      !navigation_handle->HasCommitted() ||
      navigation_handle->IsSameDocument()) {
    return;
  }

  had_same_origin_navigation_ = navigation_handle->IsSameOrigin();

  if (!navigation_handle->IsErrorPage()) {
    permissions::PermissionUmaUtil::
        RecordTopLevelPermissionsHeaderPolicyOnNavigation(
            navigation_handle->GetRenderFrameHost());
  }

  if (!base::FeatureList::IsEnabled(
          features::kBackForwardCacheUnblockPermissionRequest)) {
    if (!pending_permission_requests_.IsEmpty() || IsRequestInProgress()) {
      // |pending_permission_requests_| and |requests_| will be deleted below,
      // which might be a problem for back-forward cache — the page might be
      // restored later, but the requests won't be. Disable bfcache here if we
      // have any requests here to prevent this from happening.
      content::BackForwardCache::DisableForRenderFrameHost(
          navigation_handle->GetPreviousRenderFrameHostId(),
          back_forward_cache::DisabledReason(
              back_forward_cache::DisabledReasonId::kPermissionRequestManager));
    }
  }

  // `CleanUpRequests()` will update activity indicators. `DidFinishNavigation`
  // means that a new document was recently created, it should not display
  // blocked indicators from a previous document.
  auto* pscs = content_settings::PageSpecificContentSettings::GetForFrame(
      web_contents()->GetPrimaryMainFrame());
  // `pscs` can be nullptr in tests.
  if (pscs) {
    pscs->OnPermissionRequestCleanupStart();
  }
  CleanUpRequests();
  if (pscs) {
    pscs->OnPermissionRequestCleanupEnd();
  }
}

void PermissionRequestManager::DocumentOnLoadCompletedInPrimaryMainFrame() {
  on_page_loaded_time_ = base::TimeTicks::Now();

  // This is scheduled because while all calls to the browser have been
  // issued at DOMContentLoaded, they may be bouncing around in scheduled
  // callbacks finding the UI thread still. This makes sure we allow those
  // scheduled calls to AddRequest to complete before we show the page-load
  // permissions prompt.
  ScheduleDequeueRequestIfNeeded();
}

void PermissionRequestManager::DOMContentLoaded(
    content::RenderFrameHost* render_frame_host) {
  ScheduleDequeueRequestIfNeeded();
}

void PermissionRequestManager::WebContentsDestroyed() {
  // If the web contents has been destroyed, treat the prompt as cancelled.
  CleanUpRequests();

  // The WebContents is going away; be aggressively paranoid and delete
  // ourselves lest other parts of the system attempt to add permission
  // prompts or use us otherwise during the destruction.
  web_contents()->RemoveUserData(UserDataKey());
  // That was the equivalent of "delete this". This object is now destroyed;
  // returning from this function is the only safe thing to do.
}

void PermissionRequestManager::OnVisibilityChanged(
    content::Visibility visibility) {
  // If `tab_subscriptions_` isn't empty, defer to those listeners instead.
  if (!tab_subscriptions_.empty()) {
    return;
  }
  bool prior_tab_is_active_ = tab_is_active_;
  tab_is_active_ = visibility != content::Visibility::HIDDEN;
  if (prior_tab_is_active_ != tab_is_active_) {
    OnTabActiveChanged();
  }
}

const std::vector<std::unique_ptr<PermissionRequest>>&
PermissionRequestManager::Requests() const {
  return requests_;
}

GURL PermissionRequestManager::GetRequestingOrigin() const {
  if (requesting_origin_for_testing_.has_value()) {
    return requesting_origin_for_testing_.value();
  }

  CHECK(!requests_.empty());
  GURL origin = requests_.front()->requesting_origin();
  if (DCHECK_IS_ON()) {
    for (const auto& request : requests_) {
      DCHECK_EQ(origin, request->requesting_origin());
    }
  }
  return origin;
}

GURL PermissionRequestManager::GetEmbeddingOrigin() const {
  if (embedding_origin_for_testing_.has_value()) {
    return embedding_origin_for_testing_.value();
  }

  return PermissionUtil::GetLastCommittedOriginAsURL(
      web_contents()->GetPrimaryMainFrame());
}

void PermissionRequestManager::Accept(const PromptOptions& prompt_options) {
  CHECK(requests_[0]->GetContentSettingsType() ==
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS ||
        std::holds_alternative<std::monostate>(prompt_options))
      << "Requests that are not for Geolocation with options should not "
         "pass any options (must be std::monostate)."
      << requests_[0]->GetContentSettingsType();

  if (ignore_callbacks_from_prompt_) {
    return;
  }
  DCHECK(view_);
  base::AutoReset<bool> block_preempt(&can_preempt_current_request_, false);
  PermissionAction action = PermissionAction::GRANTED;

  for (const auto& request : requests_) {
    StorePermissionActionForUMA(request->requesting_origin(),
                                request->request_type(), action);
    PermissionGrantedIncludingDuplicates(request.get(), prompt_options,
                                         /*is_one_time=*/false);

#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
    std::optional<ContentSettingsType> content_settings_type =
        RequestTypeToContentSettingsType(request->request_type());
    if (content_settings_type.has_value()) {
      PermissionUmaUtil::RecordPermissionRegrantForUnusedSites(
          request->requesting_origin(), content_settings_type.value(),
          PermissionSourceUI::PROMPT, web_contents()->GetBrowserContext(),
          base::Time::Now());
    }
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  }

  NotifyRequestDecided(action);
  CurrentRequestsDecided(action, prompt_options);
}

void PermissionRequestManager::AcceptThisTime(
    const PromptOptions& prompt_options) {
  CHECK(requests_[0]->GetContentSettingsType() ==
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS ||
        std::holds_alternative<std::monostate>(prompt_options))
      << "Requests that are not for Geolocation with options should not "
         "pass any options (must be std::monostate).";

  if (ignore_callbacks_from_prompt_) {
    return;
  }
  DCHECK(view_);
  base::AutoReset<bool> block_preempt(&can_preempt_current_request_, false);

  PermissionAction action = PermissionAction::GRANTED_ONCE;
  for (const auto& request : requests_) {
    StorePermissionActionForUMA(request->requesting_origin(),
                                request->request_type(), action);
    PermissionGrantedIncludingDuplicates(request.get(), prompt_options,
                                         /*is_one_time=*/true);
  }

  NotifyRequestDecided(action);
  CurrentRequestsDecided(action, prompt_options);
}

void PermissionRequestManager::Deny(const PromptOptions& prompt_options) {
  CHECK(requests_[0]->GetContentSettingsType() ==
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS ||
        std::holds_alternative<std::monostate>(prompt_options))
      << "Requests that are not for Geolocation with options should not "
         "pass any options (must be std::monostate).";

  if (ignore_callbacks_from_prompt_) {
    return;
  }
  DCHECK(view_);
  base::AutoReset<bool> block_preempt(&can_preempt_current_request_, false);

  // Suppress any further prompts in this WebContents, from any origin, until
  // there is a user-initiated navigation. This stops users from getting
  // trapped in request loops where the website automatically navigates
  // cross-origin (e.g. to another subdomain) to be able to prompt again after
  // a rejection.
  if (std::ranges::contains(requests_, ContentSettingsType::NOTIFICATIONS,
                            &PermissionRequest::GetContentSettingsType)) {
    is_notification_prompt_cooldown_active_ = true;
  }

  PermissionAction action = PermissionAction::DENIED;
  for (const auto& request : requests_) {
    StorePermissionActionForUMA(request->requesting_origin(),
                                request->request_type(), action);
    PermissionDeniedIncludingDuplicates(request.get());
  }

  NotifyRequestDecided(action);
  CurrentRequestsDecided(action, prompt_options);
}

void PermissionRequestManager::Dismiss(const PromptOptions& prompt_options) {
  CHECK(requests_[0]->GetContentSettingsType() ==
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS ||
        std::holds_alternative<std::monostate>(prompt_options))
      << "Requests that are not for Geolocation with options should not "
         "pass any options (must be std::monostate).";

  if (ignore_callbacks_from_prompt_) {
    return;
  }
  DCHECK(view_);
  base::AutoReset<bool> block_preempt(&can_preempt_current_request_, false);

  PermissionAction action = PermissionAction::DISMISSED;
  for (const auto& request : requests_) {
    StorePermissionActionForUMA(request->requesting_origin(),
                                request->request_type(), action);
    CancelRequestIncludingDuplicates(request.get());
  }

  NotifyRequestDecided(action);
  CurrentRequestsDecided(action, prompt_options);
}

void PermissionRequestManager::Ignore(const PromptOptions& prompt_options) {
  CHECK(requests_[0]->GetContentSettingsType() ==
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS ||
        std::holds_alternative<std::monostate>(prompt_options))
      << "Requests that are not for Geolocation with options should not "
         "pass any options (must be std::monostate).";

  if (ignore_callbacks_from_prompt_) {
    return;
  }
  base::AutoReset<bool> block_preempt(&can_preempt_current_request_, false);

  PermissionAction action = PermissionAction::IGNORED;
  for (const auto& request : requests_) {
    StorePermissionActionForUMA(request->requesting_origin(),
                                request->request_type(), action);
    CancelRequestIncludingDuplicates(request.get());
  }

  NotifyRequestDecided(action);
  CurrentRequestsDecided(action, prompt_options);
}

void PermissionRequestManager::FinalizeCurrentRequests() {
  CHECK(IsRequestInProgress());
  std::vector<std::unique_ptr<PermissionRequest>> postponed_requests;
  if (embedded_prompt_flow_model_) {
    postponed_requests = embedded_prompt_flow_model_->TakePostponedRequests();
  }
  ResetViewStateForCurrentRequest();

  {
    base::AutoReset<bool> block_preempt(&can_preempt_current_request_, false);

    //  Erase the request from |validated_requests_| before its destruction
    //  during requests_.clear() at the end of this function.
    for (const auto& request : requests_) {
      std::erase(validated_requests_, *request);
      request_sources_map_.erase(base::raw_ref(*request));
      FinishRequestIncludingDuplicates(request.get());
    }

    // No need to execute the preignore logic as we canceling currently active
    // requests anyway.
    preignore_timer_.Stop();
  }

  requests_.clear();

  for (Observer& observer : observer_list_) {
    observer.OnRequestsFinalized();
  }

  // If there are any postponed requests from an embedded permission prompt,
  // make sure they are resolved.
  if (!postponed_requests.empty()) {
    for (auto& request : postponed_requests) {
      StorePermissionActionForUMA(request->requesting_origin(),
                                  request->request_type(),
                                  PermissionAction::DISMISSED);
      FinalizeAndCancelRequest(*request);
    }
  }
  ScheduleDequeueRequestIfNeeded();
}

void PermissionRequestManager::OpenHelpCenterLink(const ui::Event& event) {
  CHECK_GT(requests_.size(), 0u);
  switch (requests_[0]->request_type()) {
    case permissions::RequestType::kStorageAccess:
      GetAssociatedWebContents()->OpenURL(
          content::OpenURLParams(
              GURL(permissions::kEmbeddedContentHelpCenterURL),
              content::Referrer(),
              ui::DispositionFromEventFlags(
                  event.flags(), WindowOpenDisposition::NEW_FOREGROUND_TAB),
              ui::PAGE_TRANSITION_LINK, /*is_renderer_initiated=*/false),
          /*navigation_handle_callback=*/{});
      break;
    default:
      NOTREACHED();
  }
}

void PermissionRequestManager::PreIgnoreQuietPrompt() {
  // Random number of seconds in the range [1.0, 2.0).
  double delay_seconds = 1.0 + 1.0 * base::RandDouble();
  preignore_timer_.Start(
      FROM_HERE, base::Seconds(delay_seconds), this,
      &PermissionRequestManager::PreIgnoreQuietPromptInternal);
}

void PermissionRequestManager::PreIgnoreQuietPromptInternal() {
  DCHECK(!requests_.empty());

  if (requests_.empty()) {
    // If `requests_` was cleared then there is nothing preignore.
    return;
  }

  for (const auto& request : requests_) {
    CancelRequestIncludingDuplicates(request.get(),
                                     /*is_final_decision=*/false);
  }

  blink::PermissionType permission;
  bool success = PermissionUtil::GetPermissionType(
      requests_[0]->GetContentSettingsType(), &permission);
  DCHECK(success);

  PermissionUmaUtil::PermissionRequestPreignored(permission);
}

bool PermissionRequestManager::WasCurrentRequestAlreadyDisplayed() {
  return current_request_already_displayed_;
}

void PermissionRequestManager::SetDismissOnTabClose() {
  should_dismiss_current_request_ = true;
}

void PermissionRequestManager::SetPromptShown() {
  did_show_prompt_ = true;
}

void PermissionRequestManager::SetDecisionTime() {
  current_request_decision_time_ = base::Time::Now();
}

void PermissionRequestManager::SetManageClicked() {
  set_manage_clicked();
}

void PermissionRequestManager::SetLearnMoreClicked() {
  set_learn_more_clicked();
}

base::WeakPtr<PermissionPrompt::Delegate>
PermissionRequestManager::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

content::WebContents* PermissionRequestManager::GetAssociatedWebContents() {
  content::WebContents& web_contents = GetWebContents();
  return &web_contents;
}

bool PermissionRequestManager::RecreateView() {
  const bool should_do_auto_response_for_testing =
      (current_request_prompt_disposition_ ==
       PermissionPromptDisposition::MAC_OS_PROMPT);
  // Check that a request is filed, and the surface is allowlisted or
  // has an initiated permission prompt. If a flow model is not created even
  // though a permission prompt has been requested, it will cause a crash.
  if (PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI(
          this, web_contents())) {
    if (!embedded_prompt_flow_model_) {
      embedded_prompt_flow_model_ =
          std::make_unique<EmbeddedPermissionPromptFlowModel>(web_contents(),
                                                              this);
    }
    CalculateCurrentVariantForEmbeddedPrompt();
  }
  view_ = view_factory_.Run(web_contents(), this);
  if (!view_) {
    current_request_prompt_disposition_ =
        PermissionPromptDisposition::NONE_VISIBLE;
    if (ShouldDropCurrentRequestIfCannotShowQuietly()) {
      CurrentRequestsDecided(PermissionAction::IGNORED,
                             /*prompt_options=*/std::monostate());
      // Use `ShouldCurrentRequestUsePermissionElementSecondaryUI` to also
      // include allowlisted surfaces.
    } else if (PermissionUtil::
                   ShouldCurrentRequestUsePermissionElementSecondaryUI(
                       this, web_contents()) ||
               IsCurrentRequestExclusiveAccess()) {
      Ignore(/*prompt_options=*/std::monostate());
    }
    NotifyPromptRecreateFailed();
    return false;
  }
  current_request_prompt_disposition_ = view_->GetPromptDisposition();
  current_request_pepc_prompt_position_ = view_->GetPromptPosition();
  SetCurrentRequestsInitialStatuses();

  if (auto_response_for_test_ != NONE && should_do_auto_response_for_testing) {
    // MAC_OS_PROMPT disposition has it's own auto-response logic for testing,
    // so if that was the original disposition we would have skipped our own
    // auto-response logic. Since the disposition can have changed, trigger
    // a possible auto response again here.
    DoAutoResponseForTesting();  // IN-TEST
  }
  return true;
}

const PermissionPrompt* PermissionRequestManager::GetCurrentPrompt() const {
  return view_.get();
}

EmbeddedPermissionPromptFlowModel*
PermissionRequestManager::GetEmbeddedPromptFlowModel() const {
  return embedded_prompt_flow_model_.get();
}

void PermissionRequestManager::CalculateCurrentVariantForEmbeddedPrompt() {
  CHECK(embedded_prompt_flow_model_);
  embedded_prompt_flow_model_->CalculateCurrentVariant(requests_);
}

void PermissionRequestManager::AdvanceOrFinalizeEmbeddedPromptFlow() {
  CHECK(embedded_prompt_flow_model_);

  CalculateCurrentVariantForEmbeddedPrompt();
  using Variant = EmbeddedPermissionPromptFlowModel::Variant;
  const Variant variant = embedded_prompt_flow_model_->prompt_variant();
  if (variant != Variant::kPreviouslyGranted &&
      variant != Variant::kUninitialized) {
    if (view_) {
      DeletePrompt();
    }
    RecreateView();
    return;
  }

  // Make sure all request callbacks run. If a request was already accepted,
  // this won't do anything for that request.
  for (const std::unique_ptr<PermissionRequest>& request : requests_) {
    request->Cancelled(/*is_final_decision=*/false);
  }
  FinalizeCurrentRequests();
}

GeolocationAccuracy
PermissionRequestManager::GetInitialGeolocationAccuracySelection() const {
  static constexpr GeolocationAccuracy kDefaultAccuracy =
      GeolocationAccuracy::kPrecise;
  // `current_request_ui_to_use_` is not yet populated when the WebContents is
  // destroyed while the asynchronous `PermissionUiSelector` evaluations were
  // still in flight.
  if (!base::FeatureList::IsEnabled(
          features::kPermissionPredictionsGeolocationAccuracy) ||
      !current_request_ui_to_use_.has_value()) {
    return kDefaultAccuracy;
  }
  switch (current_request_ui_to_use_->geolocation_accuracy) {
    case PermissionUiSelector::GeolocationAccuracy::kUnspecified:
      return kDefaultAccuracy;
    case PermissionUiSelector::GeolocationAccuracy::kPrecise:
      return GeolocationAccuracy::kPrecise;
    case PermissionUiSelector::GeolocationAccuracy::kApproximate:
      return GeolocationAccuracy::kApproximate;
    default:
      NOTREACHED();
  }
}

std::optional<GeolocationPromptType>
PermissionRequestManager::GetGeolocationPromptType() const {
  CHECK_EQ(requests_.size(), 1u);
  return requests_[0]->GetGeolocationPromptType();
}

std::optional<gfx::Rect>
PermissionRequestManager::GetPromptBubbleViewBoundsInScreen() const {
  return view_ ? view_->GetViewBoundsInScreen() : std::nullopt;
}

PermissionRequestManager::PermissionRequestManager(
    content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      content::WebContentsUserData<PermissionRequestManager>(*web_contents),
      view_factory_(base::BindRepeating(&PermissionPrompt::Create)),
      auto_response_for_test_(NONE),
      permission_ui_selectors_(
          PermissionsClient::Get()->CreatePermissionUiSelectors(
              web_contents->GetBrowserContext())) {
  // Only register TabInterface observers on desktop to support Split View.
  AdoptTabInterfaceIfNeeded(GetTabInterface(web_contents));
  if (!tab_insert_subscription_) {
    tab_is_active_ =
        web_contents->GetVisibility() != content::Visibility::HIDDEN;
  }
}

void PermissionRequestManager::DequeueRequestIfNeeded() {
  // TODO(olesiamarukhno): Media requests block other media requests from
  // pre-empting them. For example, when a camera request is pending and mic
  // is requested, the camera request remains pending and mic request appears
  // only after the camera request is resolved. This is caused by code in
  // PermissionBubbleMediaAccessHandler and UserMediaClient. We probably don't
  // need two permission queues, so resolve the duplication.
  if (web_contents()->HasUncommittedNavigationInPrimaryMainFrame() || view_ ||
      IsRequestInProgress()) {
    return;
  }

  // Find first valid request.
  while (!pending_permission_requests_.IsEmpty()) {
    auto next = pending_permission_requests_.Pop();
    if (HasActiveSourceFrameOrDisallowActivationOtherwise(*next)) {
      validated_requests_.push_back(
          base::raw_ref<PermissionRequest>::from_ptr(next.get()));
      requests_.push_back(std::move(next));
      break;
    }
    FinalizeAndCancelRequest(*next);
  }

  if (requests_.empty()) {
    return;
  }

  // Find additional requests that can be grouped with the first one.
  for (; !pending_permission_requests_.IsEmpty();) {
    PermissionRequest* front = pending_permission_requests_.Peek();
    if (!HasActiveSourceFrameOrDisallowActivationOtherwise(*front)) {
      FinalizeAndCancelRequest(*front);
      continue;
    }

    validated_requests_.push_back(
        base::raw_ref<PermissionRequest>::from_ptr(front));
    if (!ShouldGroupRequests(requests_.front().get(), front)) {
      break;
    }

    requests_.push_back(pending_permission_requests_.Pop());
  }

  // Mark the remaining pending requests as validated, so only the "new and has
  // not been validated" requests added to the queue could have effect to
  // priority order
  for (const auto& request_list : pending_permission_requests_) {
    for (auto& request : request_list) {
      if (HasActiveSourceFrameOrDisallowActivationOtherwise(*request)) {
        validated_requests_.push_back(
            base::raw_ref<PermissionRequest>::from_ptr(request.get()));
      }
    }
  }

  // If the "PermissionsGestureGatedPrompts" feature is enabled, we ensure that
  // specific permission requests (currently Notifications and Geolocation) are
  // accompanied by a valid user gesture. If the user gesture is lacking we mute
  // the request.
  if (base::FeatureList::IsEnabled(
          permissions::features::kPermissionsGestureGatedPrompts)) {
    if (auto request = requests_.front().get()) {
      bool same_origin_excluded =
          permissions::feature_params::
              kPermissionsGestureGatedPromptsExcludeSameOriginNavigations
                  .Get() &&
          had_same_origin_navigation_;
      // We explicitly don't mute requests initiated by an embedded permission
      // element (e.g., the <permission> HTML tag). This is because interaction
      // with such an element constitutes a specific, high-intent user signal
      // that justifies showing the prompt.
      if (!requests_.front()->IsEmbeddedPermissionElementInitiated() &&
          request->GetGestureType() ==
              PermissionRequestGestureType::NO_GESTURE &&
          !same_origin_excluded) {
        if ((request->request_type() == RequestType::kNotifications &&
             permissions::feature_params::
                 kPermissionsGestureGatedPromptsMuteNotifications.Get()) ||
            (request->request_type() == RequestType::kGeolocation &&
             permissions::feature_params::
                 kPermissionsGestureGatedPromptsMuteGeolocation.Get())) {
          current_request_ui_to_use_ =
              PermissionUiSelector::Decision::UseQuietUi(
                  QuietUiReason::kTriggeredDueToLackOfGesture,
                  PermissionUiSelector::Decision::ShowNoWarning());
          ShowPrompt();
          return;
        }
      }
    }
  }

  if (permission_ui_selectors_.empty()) {
    current_request_ui_to_use_ = UiDecision::UseNormalUiAndShowNoWarning();
    ShowPrompt();
    return;
  }

  DCHECK(!current_request_ui_to_use_.has_value());
  // Initialize the selector decisions vector.
  DCHECK(selector_decisions_.empty());
  selector_decisions_.resize(permission_ui_selectors_.size());

  for (size_t selector_index = 0;
       selector_index < permission_ui_selectors_.size(); ++selector_index) {
    // Skip if we have already made a decision due to a higher priority
    // selector
    if (current_request_ui_to_use_.has_value() || !IsRequestInProgress()) {
      break;
    }

    if (!requests_.front()->IsEmbeddedPermissionElementInitiated() &&
        permission_ui_selectors_[selector_index]->IsPermissionRequestSupported(
            requests_.front()->request_type())) {
      permission_ui_selectors_[selector_index]->SelectUiToUse(
          web_contents(), requests_.front().get(),
          base::BindOnce(&PermissionRequestManager::OnPermissionUiSelectorDone,
                         weak_factory_.GetWeakPtr(), selector_index));
      continue;
    }

    OnPermissionUiSelectorDone(
        selector_index,
        PermissionUiSelector::Decision::UseNormalUiAndShowNoWarning());
  }
}

void PermissionRequestManager::ScheduleDequeueRequestIfNeeded() {
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&PermissionRequestManager::DequeueRequestIfNeeded,
                     weak_factory_.GetWeakPtr()));
}

void PermissionRequestManager::ShowPrompt() {
  // There is a race condition where the request might have been removed
  // already so double-checking that there is a request in progress.
  //
  // There is no need to show a new prompt if the previous one still exists.
  if (!IsRequestInProgress() || view_) {
    return;
  }

  DCHECK(!web_contents()->HasUncommittedNavigationInPrimaryMainFrame());
  DCHECK(current_request_ui_to_use_);

  if (!tab_is_active_) {
    NotifyPromptCreationFailedHiddenTab();
    return;
  }

  // We check `requests_.empty()` after some following calls
  // (`ReprioritizeCurrentRequestIfNeeded` and `RecreateView`) to prevent
  // accidentally finalizing the requests, which could be triggered in the
  // callback chains or error handling (e.g the factory implementation can't
  // show a permission prompt).
  if (!ReprioritizeCurrentRequestIfNeeded() || requests_.empty()) {
    return;
  }

  if (!RecreateView() || requests_.empty()) {
    return;
  }

  if (!current_request_already_displayed_) {
    PermissionUmaUtil::PermissionPromptShown(requests_);

    if (!requests_.empty()) {
#if BUILDFLAG(IS_ANDROID)
      if (requests_[0]->GetContentSettingsType() ==
          ContentSettingsType::NOTIFICATIONS) {
        has_requested_notifications_ = true;
      }
#endif  // BUILDFLAG(IS_ANDROID)

      // The session duration before a permission prompt is displayed is only
      // recorded for geolocation and notifications requests because these two
      // permission types are supported by the PermissionsAI and potentially can
      // be impacted by prompt muting.
      //
      // `on_page_loaded_time_` is not reset because it is ok to record the
      // session duration multiple times per permission type for the same page
      // load.
      if (requests_[0]->request_type() == RequestType::kGeolocation ||
          requests_[0]->GetContentSettingsType() ==
              ContentSettingsType::NOTIFICATIONS) {
        PermissionUmaUtil::RecordPrePromptSessionDuration(
            requests_[0]->GetContentSettingsType(), on_page_loaded_time_);
      }

      if (requests_[0]->GetContentSettingsType() ==
          ContentSettingsType::NOTIFICATIONS) {
        notification_request_first_display_time_ = base::TimeTicks::Now();
      } else if (requests_[0]->request_type() == RequestType::kGeolocation) {
        geolocation_request_first_display_time_ = base::TimeTicks::Now();
      }
    }

    auto quiet_ui_reason = ReasonForUsingQuietUi();
    if (quiet_ui_reason) {
      switch (*quiet_ui_reason) {
        case QuietUiReason::kEnabledInPrefs:
        case QuietUiReason::kTriggeredByCrowdDeny:
        case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
        case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
          break;
        case QuietUiReason::kTriggeredDueToAbusiveRequests:
          LogWarningToConsole(kAbusiveNotificationRequestsEnforcementMessage);
          break;
        case QuietUiReason::kTriggeredDueToAbusiveContent:
          LogWarningToConsole(kAbusiveNotificationContentEnforcementMessage);
          break;
        case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
          LogWarningToConsole(
              kDisruptiveNotificationBehaviorEnforcementMessage);
          break;
        case QuietUiReason::kTriggeredDueToLackOfGesture:
          if (requests_[0]->request_type() == RequestType::kNotifications) {
            LogWarningToConsole(kGestureGatedNotificationMessage);
          } else if (requests_[0]->request_type() ==
                     RequestType::kGeolocation) {
            LogWarningToConsole(kGestureGatedGeolocationMessage);
          }
          break;
      }
      base::RecordAction(base::UserMetricsAction(
          "Notifications.Quiet.PermissionRequestShown"));
    }

    PermissionsClient::Get()->TriggerPromptHatsSurveyIfEnabled(
        web_contents(), requests_[0]->request_type(), std::nullopt,
        DetermineCurrentRequestUIDisposition(),
        DetermineCurrentRequestUIDispositionReasonForUMA(),
        requests_[0]->GetGestureType(),
        /*prompt_display_duration=*/std::nullopt, /*is_post_prompt=*/false,
        web_contents()
            ->GetPrimaryMainFrame()
            ->GetLastCommittedOrigin()
            .GetURL(),
        current_request_pepc_prompt_position_,
        GetRequestInitialStatus(requests_[0].get()),
        hats_shown_callback_.has_value()
            ? std::move(hats_shown_callback_.value())
            : base::DoNothing(),
        /*prompt_options=*/std::monostate());

    hats_shown_callback_.reset();
  }
  current_request_already_displayed_ = true;
  current_request_first_display_time_ = base::Time::Now();

  // `NotifyPromptAdded()` MUST run after `RecreateView()` has successfully
  // created the UI view. Calling it before `RecreateView()` may break the
  // assumption that `NotifyPromptAdded` is only called when the prompt has
  // successfully shown itself, AND may break the assumption that by default,
  // other event listeners do not have to clean up the state in
  // `NotifyPromptAdded` if failure is reached before the prompt is rendered.
  NotifyPromptAdded();

  // If in testing mode, automatically respond to the bubble that was shown.
  if (auto_response_for_test_ != NONE) {
    DoAutoResponseForTesting();
  }
}

void PermissionRequestManager::SetHatsShownCallback(
    base::OnceCallback<void()> callback) {
  hats_shown_callback_ = std::move(callback);
}

void PermissionRequestManager::DeletePrompt() {
  DCHECK(view_);
  {
    base::AutoReset<bool> deleting(&ignore_callbacks_from_prompt_, true);
    view_.reset();
  }
  NotifyPromptRemoved();
}

void PermissionRequestManager::ResetViewStateForCurrentRequest() {
  for (const auto& selector : permission_ui_selectors_) {
    selector->Cancel();
  }

  current_request_already_displayed_ = false;
  current_request_first_display_time_ = base::Time();
  current_request_decision_time_ = base::Time();
  current_request_prompt_disposition_.reset();
  prediction_grant_likelihood_.reset();
  permission_request_relevance_.reset();
  permission_ai_relevance_model_.reset();
  current_request_ui_to_use_.reset();
  was_decision_held_back_.reset();
  selector_decisions_.clear();
  should_dismiss_current_request_ = false;
  did_show_prompt_ = false;
  did_click_manage_ = false;
  did_click_learn_more_ = false;
  hats_shown_callback_.reset();
  current_request_pepc_prompt_position_.reset();
  current_requests_initial_statuses_.clear();
  if (view_) {
    DeletePrompt();
  }
  embedded_prompt_flow_model_.reset();
}

bool PermissionRequestManager::ShouldRecordUmaForCurrentPrompt() const {
  return (!view_ || view_->IsAskPrompt());
}

void PermissionRequestManager::CurrentRequestsDecided(
    PermissionAction permission_action,
    const PromptOptions& prompt_options) {
  DCHECK(IsRequestInProgress());
  base::TimeDelta time_to_decision;
  if (!current_request_first_display_time_.is_null() &&
      permission_action != PermissionAction::IGNORED) {
    if (current_request_decision_time_.is_null()) {
      current_request_decision_time_ = base::Time::Now();
    }
    time_to_decision =
        current_request_decision_time_ - current_request_first_display_time_;
  }

  if (time_to_decision_for_test_.has_value()) {
    time_to_decision = time_to_decision_for_test_.value();
    time_to_decision_for_test_.reset();
  }

  std::optional<permissions::PermissionIgnoredReason> ignore_reason =
      std::nullopt;
#if !BUILDFLAG(IS_ANDROID)
  // ignore reason metric currently not supported on android
  if (permission_action == PermissionAction::IGNORED) {
    ignore_reason = std::make_optional(
        PermissionsClient::Get()->DetermineIgnoreReason(web_contents()));
  }
#endif

  content::BrowserContext* browser_context =
      web_contents()->GetBrowserContext();

  if (ShouldRecordUmaForCurrentPrompt()) {
    PermissionUmaUtil::PermissionPromptResolved(
        requests_, browser_context, permission_action, prompt_options,
        time_to_decision, DetermineCurrentRequestUIDisposition(),
        DetermineCurrentRequestUIDispositionReasonForUMA(),
        view_ ? std::optional(view_->GetPromptVariants()) : std::nullopt,
        prediction_grant_likelihood_, permission_request_relevance_,
        permission_ai_relevance_model_, was_decision_held_back_, ignore_reason,
        did_show_prompt_, did_click_manage_, did_click_learn_more_,
        requests_[0]->GetContentSettingsType() ==
                ContentSettingsType::GEOLOCATION_WITH_OPTIONS
            ? std::make_optional<GeolocationAccuracy>(
                  GetInitialGeolocationAccuracySelection())
            : std::nullopt);
  }

  std::optional<QuietUiReason> quiet_ui_reason;
  if (ShouldCurrentRequestUseQuietUI()) {
    quiet_ui_reason = ReasonForUsingQuietUi();
  }

  for (auto& request : requests_) {
    // TODO(timloh): We only support dismiss and ignore embargo for
    // permissions which use PermissionRequestImpl as the other subclasses
    // don't support GetContentSettingsType.
    if (request->GetContentSettingsType() == ContentSettingsType::DEFAULT) {
      continue;
    }

    auto time_since_shown =
        current_request_first_display_time_.is_null()
            ? base::TimeDelta::Max()
            : base::Time::Now() - current_request_first_display_time_;
    PermissionsClient::Get()->OnPromptResolved(
        request.get(), permission_action, prompt_options,
        DetermineCurrentRequestUIDisposition(),
        DetermineCurrentRequestUIDispositionReasonForUMA(), quiet_ui_reason,
        time_since_shown, current_request_pepc_prompt_position_,
        GetRequestInitialStatus(request.get()), web_contents());

    PermissionUmaUtil::RecordEmbargoStatus(RecordActionAndGetEmbargoStatus(
        browser_context, request.get(), permission_action));
    PermissionActionsHistory* actions_history =
        PermissionsClient::Get()->GetPermissionActionsHistory(browser_context);
    if (request->IsEligibleForHeuristicAutoGrant()) {
      if (permission_action == PermissionAction::GRANTED_ONCE) {
        actions_history->RecordTemporaryGrant(
            request->requesting_origin(), request->GetContentSettingsType());
      } else if (permission_action == PermissionAction::DISMISSED) {
        actions_history->ResetHeuristicData(request->requesting_origin(),
                                            request->GetContentSettingsType());
      }
      // TODO(crbug.com/446603274): Record metrics of geolocation PEPC
      // request.
    }

    ContentSettingsType content_settings_type =
        request->GetContentSettingsType();
    if (content_settings_type == ContentSettingsType::GEOLOCATION ||
        content_settings_type ==
            ContentSettingsType::GEOLOCATION_WITH_OPTIONS ||
        content_settings_type == ContentSettingsType::MEDIASTREAM_CAMERA ||
        content_settings_type == ContentSettingsType::MEDIASTREAM_MIC) {
      if (permission_action == PermissionAction::GRANTED_ONCE) {
        actions_history->RecordOneTimeGrant(request->requesting_origin(),
                                            content_settings_type);
      } else if (permission_action == PermissionAction::GRANTED ||
                 permission_action == PermissionAction::DENIED ||
                 permission_action == PermissionAction::DISMISSED ||
                 permission_action == PermissionAction::IGNORED) {
        actions_history->RecordOTPCountForAction(
            content_settings_type, permission_action,
            actions_history->GetOneTimeGrantCount(request->requesting_origin(),
                                                  content_settings_type));
      }
    }
  }

  if (auto_response_for_test_ == NONE && embedded_prompt_flow_model_ &&
      (permission_action == PermissionAction::GRANTED ||
       permission_action == PermissionAction::GRANTED_ONCE)) {
    // If an embedded permission prompt was granted, we might need to display
    // additional variants of the prompt for the same requests.
    AdvanceOrFinalizeEmbeddedPromptFlow();
    return;
  }

  FinalizeCurrentRequests();
}

void PermissionRequestManager::CleanUpRequests() {
#if BUILDFLAG(IS_ANDROID)
  has_requested_notifications_ = false;
#endif  // BUILDFLAG(IS_ANDROID)

  // No need to execute the preignore logic as we canceling currently active
  // requests anyway.
  preignore_timer_.Stop();

  for (; !pending_permission_requests_.IsEmpty();
       pending_permission_requests_.Pop()) {
    auto* pending_request = pending_permission_requests_.Peek();
    std::erase(validated_requests_, *pending_request);
    request_sources_map_.erase(
        base::raw_ref<PermissionRequest>::from_ptr(pending_request));
    CancelRequestIncludingDuplicates(pending_request);
    FinishRequestIncludingDuplicates(pending_request);
  }

  if (IsRequestInProgress()) {
    for (const auto& request : requests_) {
      CancelRequestIncludingDuplicates(request.get());
    }

    CurrentRequestsDecided(should_dismiss_current_request_
                               ? PermissionAction::DISMISSED
                               : PermissionAction::IGNORED,
                           /*prompt_options=*/std::monostate());
    should_dismiss_current_request_ = false;
  }
}

PermissionRequest* PermissionRequestManager::GetExistingRequest(
    PermissionRequest* request) const {
  for (const auto& existing_request : requests_) {
    if (request->IsDuplicateOf(existing_request.get())) {
      return existing_request.get();
    }
  }
  return pending_permission_requests_.FindDuplicate(request);
}

PermissionRequestManager::PermissionRequestList::iterator
PermissionRequestManager::FindDuplicateRequestList(PermissionRequest* request) {
  for (auto request_list = duplicate_requests_.begin();
       request_list != duplicate_requests_.end(); ++request_list) {
    for (auto iter = request_list->begin(); iter != request_list->end();) {
      const auto& current_request = (*iter);

      // The first valid request in the list will indicate whether all other
      // members are duplicate or not.
      if (current_request->IsDuplicateOf(request)) {
        return request_list;
      }

      break;
    }
  }

  return duplicate_requests_.end();
}

PermissionRequestManager::PermissionRequestList::iterator
PermissionRequestManager::VisitDuplicateRequests(
    DuplicateRequestVisitor visitor,
    PermissionRequest* request) {
  auto request_list = FindDuplicateRequestList(request);
  if (request_list == duplicate_requests_.end()) {
    return request_list;
  }

  for (auto iter = request_list->begin(); iter != request_list->end();) {
    if (auto& weak_request = (*iter)) {
      visitor.Run(weak_request);
      ++iter;
    } else {
      // Remove any requests that have been destroyed.
      iter = request_list->erase(iter);
    }
  }

  return request_list;
}

void PermissionRequestManager::PermissionGrantedIncludingDuplicates(
    PermissionRequest* request,
    const PromptOptions& prompt_options,
    bool is_one_time) {
  CHECK(RequestExistsExactlyOnce(request, pending_permission_requests_,
                                 requests_))
      << "Only requests in [pending_permission_]requests_ can have duplicates";
  request->PermissionGranted(prompt_options, is_one_time);
  VisitDuplicateRequests(
      base::BindRepeating(
          [](const PromptOptions& prompt_options, bool is_one_time,
             const std::unique_ptr<PermissionRequest>& request) {
            request->PermissionGranted(prompt_options, is_one_time);
          },
          prompt_options, is_one_time),
      request);
}

void PermissionRequestManager::PermissionDeniedIncludingDuplicates(
    PermissionRequest* request) {
  CHECK(RequestExistsExactlyOnce(request, pending_permission_requests_,
                                 requests_))
      << "Only requests in [pending_permission_]requests_ can have duplicates";
  request->PermissionDenied();
  VisitDuplicateRequests(
      base::BindRepeating(
          [](const std::unique_ptr<PermissionRequest>& request) {
            request->PermissionDenied();
          }),
      request);
}

void PermissionRequestManager::CancelRequestIncludingDuplicates(
    PermissionRequest* request,
    bool is_final_decision) {
  CHECK(RequestExistsExactlyOnce(request, pending_permission_requests_,
                                 requests_))
      << "Only requests in [pending_permission_]requests_ can have duplicates";
  request->Cancelled(is_final_decision);
  VisitDuplicateRequests(
      base::BindRepeating(
          [](bool is_final, const std::unique_ptr<PermissionRequest>& request) {
            request->Cancelled(is_final);
          },
          is_final_decision),
      request);
}

void PermissionRequestManager::FinishRequestIncludingDuplicates(
    PermissionRequest* request) {
  CHECK(RequestExistsExactlyOnce(request, pending_permission_requests_,
                                 requests_))
      << "Only requests in [pending_permission_]requests_ can have duplicates";
  auto duplicate_list = FindDuplicateRequestList(request);

  // Additionally, we can now remove the duplicates.
  if (duplicate_list != duplicate_requests_.end()) {
    duplicate_requests_.erase(duplicate_list);
  }
}

void PermissionRequestManager::AddObserver(Observer* observer) {
  if (!observer_list_.HasObserver(observer)) {
    observer_list_.AddObserver(observer);
  }
}

void PermissionRequestManager::RemoveObserver(Observer* observer) {
  observer_list_.RemoveObserver(observer);
}

bool PermissionRequestManager::ShouldCurrentRequestUseQuietUI() const {
  // Use `ShouldCurrentRequestUsePermissionElementSecondaryUI` to include
  // allowlisted surfaces.
  if (PermissionUtil::ShouldCurrentRequestUsePermissionElementSecondaryUI(
          this, web_contents())) {
    return false;
  }
  // ContentSettingImageModel might call into this method if the user switches
  // between tabs while the |notification_permission_ui_selectors_| are
  // pending.
  return ReasonForUsingQuietUi() != std::nullopt;
}

std::optional<PermissionRequestManager::QuietUiReason>
PermissionRequestManager::ReasonForUsingQuietUi() const {
  if (!IsRequestInProgress() || !current_request_ui_to_use_ ||
      !current_request_ui_to_use_->quiet_ui_reason) {
    return std::nullopt;
  }

  return *(current_request_ui_to_use_->quiet_ui_reason);
}

bool PermissionRequestManager::IsRequestInProgress() const {
  return !requests_.empty();
}

bool PermissionRequestManager::CanRestorePrompt() {
#if BUILDFLAG(IS_ANDROID)
  return false;
#else
  return IsRequestInProgress() &&
         current_request_prompt_disposition_.has_value() && !view_;
#endif
}

void PermissionRequestManager::RestorePrompt() {
  if (CanRestorePrompt()) {
    ShowPrompt();
  }
}

bool PermissionRequestManager::ShouldDropCurrentRequestIfCannotShowQuietly()
    const {
  std::optional<QuietUiReason> quiet_ui_reason = ReasonForUsingQuietUi();
  if (quiet_ui_reason.has_value()) {
    switch (quiet_ui_reason.value()) {
      case QuietUiReason::kEnabledInPrefs:
      case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
      case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
      case QuietUiReason::kTriggeredByCrowdDeny:
      case QuietUiReason::kTriggeredDueToLackOfGesture:
        return false;
      case QuietUiReason::kTriggeredDueToAbusiveRequests:
      case QuietUiReason::kTriggeredDueToAbusiveContent:
      case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
        return true;
    }
  }

  return false;
}

void PermissionRequestManager::NotifyTabActiveChanged(bool is_active) {
  for (Observer& observer : observer_list_) {
    observer.OnTabActiveChanged(is_active);
  }
}

void PermissionRequestManager::NotifyPromptAdded() {
  for (Observer& observer : observer_list_) {
    observer.OnPromptAdded();
  }
}

void PermissionRequestManager::NotifyPromptRemoved() {
  for (Observer& observer : observer_list_) {
    observer.OnPromptRemoved();
  }
}

void PermissionRequestManager::NotifyPromptRecreateFailed() {
  for (Observer& observer : observer_list_) {
    observer.OnPromptRecreateViewFailed();
  }
}

void PermissionRequestManager::NotifyPromptCreationFailedHiddenTab() {
  for (Observer& observer : observer_list_) {
    observer.OnPromptCreationFailedHiddenTab();
  }
}

void PermissionRequestManager::NotifyRequestDecided(
    permissions::PermissionAction permission_action) {
  for (Observer& observer : observer_list_) {
    observer.OnRequestDecided(permission_action);
  }
}

void PermissionRequestManager::StorePermissionActionForUMA(
    const GURL& origin,
    RequestType request_type,
    PermissionAction permission_action) {
  if (!ShouldRecordUmaForCurrentPrompt()) {
    return;
  }

  std::optional<ContentSettingsType> content_settings_type =
      RequestTypeToContentSettingsType(request_type);
  if (content_settings_type.has_value()) {
    PermissionsClient::Get()
        ->GetOriginKeyedPermissionActionService(
            web_contents()->GetBrowserContext())
        ->RecordAction(PermissionUtil::GetLastCommittedOriginAsURL(
                           web_contents()->GetPrimaryMainFrame()),
                       content_settings_type.value(), permission_action);
  }
}

std::optional<PermissionRequestManager::UiDecision>
PermissionRequestManager::TakePermissionUiDecisionIfReady() {
  using GeolocationAccuracy = PermissionUiSelector::GeolocationAccuracy;
  GeolocationAccuracy first_selected_geolocation_accuracy =
      GeolocationAccuracy::kUnspecified;

  for (size_t i = 0; i < selector_decisions_.size(); i++) {
    const std::optional<UiDecision>& decision = selector_decisions_[i];
    const std::unique_ptr<PermissionUiSelector>& selector =
        permission_ui_selectors_[i];

    if (!decision.has_value()) {
      // We should wait for all higher priority selectors before taking a
      // decision.
      return std::nullopt;
    }

    if (selector->IsPermissionRequestSupported(
            requests_.front()->request_type())) {
      if (!prediction_grant_likelihood_.has_value()) {
        prediction_grant_likelihood_ =
            selector->PredictedGrantLikelihoodForUKM();
      }

      if (!permission_request_relevance_.has_value()) {
        permission_request_relevance_ =
            selector->PermissionRequestRelevanceForUKM();
      }

      if (!permission_ai_relevance_model_.has_value()) {
        permission_ai_relevance_model_ =
            selector->PermissionAiRelevanceModelForUKM();
      }

      if (!was_decision_held_back_.has_value()) {
        was_decision_held_back_ = selector->WasSelectorDecisionHeldback();
      }
    }

    if (decision->quiet_ui_reason.has_value()) {
      // If all higher priority selectors are done, we select the first quiet UI
      // decision.
      return decision;
    }
    if (decision->geolocation_accuracy != GeolocationAccuracy::kUnspecified &&
        first_selected_geolocation_accuracy ==
            GeolocationAccuracy::kUnspecified) {
      first_selected_geolocation_accuracy = decision->geolocation_accuracy;
    }
  }
  // If all selectors are done and none was conclusive, show a normal UI.
  return UiDecision::UseNormalUi(UiDecision::ShowNoWarning(),
                                 first_selected_geolocation_accuracy);
}

void PermissionRequestManager::OnPermissionUiSelectorDone(
    size_t selector_index,
    const UiDecision& decision) {
  if (current_request_ui_to_use_.has_value()) {
    // We have already made a decision - nothing to do.
    return;
  }

  if (decision.warning_reason) {
    switch (*(decision.warning_reason)) {
      case WarningReason::kAbusiveRequests:
        LogWarningToConsole(kAbusiveNotificationRequestsWarningMessage);
        break;
      case WarningReason::kAbusiveContent:
        LogWarningToConsole(kAbusiveNotificationContentWarningMessage);
        break;
      case WarningReason::kDisruptiveBehavior:
        break;
    }
  }

  CHECK_LT(selector_index, selector_decisions_.size());
  selector_decisions_[selector_index] = decision;

  if (std::optional<UiDecision> final_decision =
          TakePermissionUiDecisionIfReady()) {
    current_request_ui_to_use_ = *final_decision;
    ShowPrompt();
  }
}

void PermissionRequestManager::SwitchToLoudPrompt() {
  current_request_ui_to_use_ =
      UiDecision::UseNormalUi(UiDecision::ShowNoWarning(),
                              current_request_ui_to_use_->geolocation_accuracy);
  view_.reset();
  ShowPrompt();
}

PermissionPromptDisposition
PermissionRequestManager::DetermineCurrentRequestUIDisposition() {
  if (current_request_prompt_disposition_.has_value()) {
    return current_request_prompt_disposition_.value();
  }
  return PermissionPromptDisposition::NONE_VISIBLE;
}

PermissionPromptDispositionReason
PermissionRequestManager::DetermineCurrentRequestUIDispositionReasonForUMA() {
  auto quiet_ui_reason = ReasonForUsingQuietUi();
  if (!quiet_ui_reason) {
    return PermissionPromptDispositionReason::DEFAULT_FALLBACK;
  }
  switch (*quiet_ui_reason) {
    case QuietUiReason::kEnabledInPrefs:
      return PermissionPromptDispositionReason::USER_PREFERENCE_IN_SETTINGS;
    case QuietUiReason::kTriggeredByCrowdDeny:
    case QuietUiReason::kTriggeredDueToAbusiveRequests:
    case QuietUiReason::kTriggeredDueToAbusiveContent:
    case QuietUiReason::kTriggeredDueToDisruptiveBehavior:
      return PermissionPromptDispositionReason::SAFE_BROWSING_VERDICT;
    case QuietUiReason::kServicePredictedVeryUnlikelyGrant:
      return PermissionPromptDispositionReason::PREDICTION_SERVICE;
    case QuietUiReason::kOnDevicePredictedVeryUnlikelyGrant:
      return PermissionPromptDispositionReason::ON_DEVICE_PREDICTION_MODEL;
    case QuietUiReason::kTriggeredDueToLackOfGesture:
      return PermissionPromptDispositionReason::LACK_OF_GESTURE;
  }
}

void PermissionRequestManager::LogWarningToConsole(const char* message) {
  web_contents()->GetPrimaryMainFrame()->AddMessageToConsole(
      blink::mojom::ConsoleMessageLevel::kWarning, message);
}

void PermissionRequestManager::DoAutoResponseForTesting() {
  // The macOS prompt has its own mechanism of auto responding.
  if (current_request_prompt_disposition_ ==
      PermissionPromptDisposition::MAC_OS_PROMPT) {
    return;
  }
  switch (auto_response_for_test_) {
    case ACCEPT_ONCE:
      AcceptThisTime(auto_response_prompt_options_for_test_);
      break;
    case ACCEPT_ALL:
      Accept(auto_response_prompt_options_for_test_);
      break;
    case DENY_ALL:
      Deny(auto_response_prompt_options_for_test_);
      break;
    case DISMISS:
      Dismiss(auto_response_prompt_options_for_test_);
      break;
    case NONE:
      NOTREACHED();
  }
}

bool PermissionRequestManager::IsCurrentRequestExclusiveAccess() const {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
  return IsRequestInProgress() &&
         IsExclusiveAccessRequest(requests_[0]->request_type());
#else
  return false;
#endif  // !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
}

PermissionEmbargoStatus
PermissionRequestManager::RecordActionAndGetEmbargoStatus(
    content::BrowserContext* browser_context,
    PermissionRequest* request,
    PermissionAction permission_action) {
  if (!request->uses_automatic_embargo()) {
    return PermissionEmbargoStatus::NOT_EMBARGOED;
  }

  PermissionDecisionAutoBlocker* const autoblocker =
      PermissionsClient::Get()->GetPermissionDecisionAutoBlocker(
          browser_context);

  if (permission_action == PermissionAction::DISMISSED &&
      !request->IsEmbeddedPermissionElementInitiated()) {
    if (autoblocker->RecordDismissAndEmbargo(
            request->requesting_origin(), request->GetContentSettingsType(),
            ShouldCurrentRequestUseQuietUI())) {
      return PermissionEmbargoStatus::REPEATED_DISMISSALS;
    }
  } else if (permission_action == PermissionAction::IGNORED &&
             !request->IsEmbeddedPermissionElementInitiated()) {
    if (autoblocker->RecordIgnoreAndEmbargo(request->requesting_origin(),
                                            request->GetContentSettingsType(),
                                            ShouldCurrentRequestUseQuietUI())) {
      return PermissionEmbargoStatus::REPEATED_IGNORES;
    }
  } else if (permission_action == PermissionAction::GRANTED_ONCE) {
    autoblocker->RemoveEmbargoAndResetCounts(request->requesting_origin(),
                                             request->GetContentSettingsType());
  }

  return PermissionEmbargoStatus::NOT_EMBARGOED;
}

void PermissionRequestManager::SetCurrentRequestsInitialStatuses() {
  // This function is called whenever the view is created which can happen
  // multiple times for the same request (e.g. by tab switching). Only actually
  // compute this if |current_requests_initial_statuses_| has been cleared
  // before to mark a view being closed.
  if (!current_requests_initial_statuses_.empty()) {
    return;
  }

  auto* map = PermissionsClient::Get()->GetSettingsMap(
      web_contents()->GetBrowserContext());
  for (const auto& request : requests_) {
    // It's possible in tests for |map| to not be initialized yet. Also there
    // are some permission requests (like SMART_CARD_DATA) which are not for
    // content settings.
    if (!map || !content_settings::ContentSettingsRegistry::GetInstance()->Get(
                    request->GetContentSettingsType())) {
      current_requests_initial_statuses_.emplace(request.get(),
                                                 CONTENT_SETTING_DEFAULT);
    } else {
      current_requests_initial_statuses_.emplace(
          request.get(),
          map->GetContentSetting(GetRequestingOrigin(), GetEmbeddingOrigin(),
                                 request->GetContentSettingsType()));
    }
  }
}

ContentSetting PermissionRequestManager::GetRequestInitialStatus(
    PermissionRequest* request) {
  auto it = current_requests_initial_statuses_.find(request);
  if (it != current_requests_initial_statuses_.end()) {
    return it->second;
  }

  return CONTENT_SETTING_DEFAULT;
}

void PermissionRequestManager::AdoptTabInterfaceIfNeeded(
    tabs::TabInterface* tab_interface) {
  if (tab_insert_subscription_ || !tab_interface) {
    return;
  }
  tab_is_active_ = tab_interface->IsActivated();
  tab_insert_subscription_ = tab_interface->RegisterDidInsert(
      base::BindRepeating(&PermissionRequestManager::OnTabAttached,
                          weak_factory_.GetWeakPtr()));
  if (tab_interface->GetBrowserWindowInterface()) {
    RegisterTabSubscriptions(tab_interface);
  }
}

void PermissionRequestManager::RegisterTabSubscriptions(
    tabs::TabInterface* tab_interface) {
  tab_subscriptions_.clear();

  tab_subscriptions_.push_back(tab_interface->RegisterDidActivate(
      base::BindRepeating(&PermissionRequestManager::OnTabActiveStatusChanged,
                          weak_factory_.GetWeakPtr(), /*is_active=*/true)));
  tab_subscriptions_.push_back(tab_interface->RegisterWillDeactivate(
      base::BindRepeating(&PermissionRequestManager::OnTabActiveStatusChanged,
                          weak_factory_.GetWeakPtr(), /*is_active=*/false)));

  tab_subscriptions_.push_back(tab_interface->RegisterWillDetach(
      base::BindRepeating(&PermissionRequestManager::OnTabDetached,
                          weak_factory_.GetWeakPtr())));

  tab_subscriptions_.push_back(tab_interface->RegisterWillDiscardContents(
      base::BindRepeating(&PermissionRequestManager::OnTabWillDiscardContents,
                          weak_factory_.GetWeakPtr())));
}

void PermissionRequestManager::OnTabActiveStatusChanged(
    bool is_active,
    tabs::TabInterface* tab_interface) {
  const bool prior_tab_is_active_ = tab_is_active_;
  tab_is_active_ = is_active;
  if (prior_tab_is_active_ != tab_is_active_) {
    OnTabActiveChanged();
  }
}

void PermissionRequestManager::OnTabDetached(
    tabs::TabInterface* tab_interface,
    tabs::TabInterface::DetachReason reason) {
  // Clear the existing tab subscriptions while the tab is detached from a tab
  // strip. This might mean the TabInterface will become part of a PWA which
  // should fall back to the OnVisibilityChanged listeners.
  tab_subscriptions_.clear();
}

void PermissionRequestManager::OnTabWillDiscardContents(
    tabs::TabInterface* tab_interface,
    content::WebContents* old_contents,
    content::WebContents* new_contents) {
  // Runs on the manager of old_contents — the only object that exists
  // before the swap and is subscribed to the tab. The replacement's manager
  // already exists (TabStripModel::DiscardWebContents() attaches tab
  // helpers before the swap) but cannot discover the TabInterface itself:
  // the TabLookupFromWebContents entry moves over only after this
  // notification returns. Hand it over explicitly.
  if (web_contents() != old_contents) {
    return;
  }
  if (PermissionRequestManager* new_manager = FromWebContents(new_contents)) {
    new_manager->AdoptTabInterfaceIfNeeded(tab_interface);
  }

  // This WebContents is leaving the tab: drop the tab wiring and fall back
  // to visibility-only gating, like OnTabDetached(). Cancelling a
  // subscription from inside its own notification is supported by
  // base::CallbackList.
  tab_subscriptions_.clear();
  tab_insert_subscription_ = base::CallbackListSubscription();
}

void PermissionRequestManager::OnTabAttached(
    tabs::TabInterface* tab_interface) {
  RegisterTabSubscriptions(tab_interface);
}

void PermissionRequestManager::OnTabActiveChanged() {
  NotifyTabActiveChanged(tab_is_active_);
  if (!tab_is_active_) {
    if (view_) {
      switch (view_->GetTabSwitchingBehavior()) {
        case PermissionPrompt::TabSwitchingBehavior::
            kDestroyPromptButKeepRequestPending: {
          DeletePrompt();
          break;
        }
        case PermissionPrompt::TabSwitchingBehavior::
            kDestroyPromptAndIgnoreRequest: {
          Ignore(/*prompt_options=*/std::monostate());
          break;
        }
        case PermissionPrompt::TabSwitchingBehavior::kKeepPromptAlive: {
          break;
        }
      }
    }

    return;
  }

  if (web_contents()->HasUncommittedNavigationInPrimaryMainFrame()) {
    return;
  }

  if (!IsRequestInProgress()) {
    ScheduleDequeueRequestIfNeeded();
    return;
  }

  if (view_) {
    // We switched tabs away and back while a prompt was active.
    DCHECK_EQ(view_->GetTabSwitchingBehavior(),
              PermissionPrompt::TabSwitchingBehavior::kKeepPromptAlive);
  } else if (current_request_ui_to_use_.has_value()) {
    ShowPrompt();
  }
}

void PermissionRequestManager::RecordPostPromptSessionDuration() {
  PermissionUmaUtil::RecordPostPromptSessionDuration(
      ContentSettingsType::NOTIFICATIONS,
      notification_request_first_display_time_);

  PermissionUmaUtil::RecordPostPromptSessionDuration(
      RequestTypeToContentSettingsType(RequestType::kGeolocation).value(),
      geolocation_request_first_display_time_);

  notification_request_first_display_time_ = base::TimeTicks();
  geolocation_request_first_display_time_ = base::TimeTicks();
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(PermissionRequestManager);

}  // namespace permissions
